An Overview of readODS

Chung-hong Chan 1

library(readODS)

You probably only need to use two functions from this package: read_ods and write_ods.

Write the data PlantGrowth (from the built-in datasets package) as a new file plant.ods in the current working directory of the user’s session.

write_ods(PlantGrowth, "plant.ods")

You can then read it back from plant.ods

read_ods("plant.ods")
#> # A tibble: 30 × 2
#>    weight group
#>     <dbl> <chr>
#>  1   4.17 ctrl 
#>  2   5.58 ctrl 
#>  3   5.18 ctrl 
#>  4   6.11 ctrl 
#>  5   4.5  ctrl 
#>  6   4.61 ctrl 
#>  7   5.17 ctrl 
#>  8   4.53 ctrl 
#>  9   5.33 ctrl 
#> 10   5.14 ctrl 
#> # ℹ 20 more rows

Update and Append

You can append another sheet into an existing ods file with the sheet name being “mtcars_ods”.

write_ods(mtcars, "plant.ods", sheet = "mtcars_ods", append = TRUE)

Read from a specific sheet. Notice row names are missing.

read_ods("plant.ods", sheet = "mtcars_ods")
#> # A tibble: 32 × 11
#>      mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
#>    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#>  1  21       6  160    110  3.9   2.62  16.5     0     1     4     4
#>  2  21       6  160    110  3.9   2.88  17.0     0     1     4     4
#>  3  22.8     4  108     93  3.85  2.32  18.6     1     1     4     1
#>  4  21.4     6  258    110  3.08  3.22  19.4     1     0     3     1
#>  5  18.7     8  360    175  3.15  3.44  17.0     0     0     3     2
#>  6  18.1     6  225    105  2.76  3.46  20.2     1     0     3     1
#>  7  14.3     8  360    245  3.21  3.57  15.8     0     0     3     4
#>  8  24.4     4  147.    62  3.69  3.19  20       1     0     4     2
#>  9  22.8     4  141.    95  3.92  3.15  22.9     1     0     4     2
#> 10  19.2     6  168.   123  3.92  3.44  18.3     1     0     4     4
#> # ℹ 22 more rows

You can also integer for sheet, e.g. 2 for the second sheet.

read_ods("plant.ods", sheet = 2)
#> # A tibble: 32 × 11
#>      mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
#>    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#>  1  21       6  160    110  3.9   2.62  16.5     0     1     4     4
#>  2  21       6  160    110  3.9   2.88  17.0     0     1     4     4
#>  3  22.8     4  108     93  3.85  2.32  18.6     1     1     4     1
#>  4  21.4     6  258    110  3.08  3.22  19.4     1     0     3     1
#>  5  18.7     8  360    175  3.15  3.44  17.0     0     0     3     2
#>  6  18.1     6  225    105  2.76  3.46  20.2     1     0     3     1
#>  7  14.3     8  360    245  3.21  3.57  15.8     0     0     3     4
#>  8  24.4     4  147.    62  3.69  3.19  20       1     0     4     2
#>  9  22.8     4  141.    95  3.92  3.15  22.9     1     0     4     2
#> 10  19.2     6  168.   123  3.92  3.44  18.3     1     0     4     4
#> # ℹ 22 more rows

Update an existing sheet and preserve row names

write_ods(mtcars, "plant.ods", sheet = "mtcars_ods", update = TRUE, row_names = TRUE)

Notice the information from the sheet mtcars_ods is updated.

read_ods("plant.ods", sheet = "mtcars_ods")
#> New names:
#> • `` -> `...1`
#> # A tibble: 32 × 12
#>    ...1          mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
#>    <chr>       <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#>  1 Mazda RX4    21       6  160    110  3.9   2.62  16.5     0     1     4     4
#>  2 Mazda RX4 …  21       6  160    110  3.9   2.88  17.0     0     1     4     4
#>  3 Datsun 710   22.8     4  108     93  3.85  2.32  18.6     1     1     4     1
#>  4 Hornet 4 D…  21.4     6  258    110  3.08  3.22  19.4     1     0     3     1
#>  5 Hornet Spo…  18.7     8  360    175  3.15  3.44  17.0     0     0     3     2
#>  6 Valiant      18.1     6  225    105  2.76  3.46  20.2     1     0     3     1
#>  7 Duster 360   14.3     8  360    245  3.21  3.57  15.8     0     0     3     4
#>  8 Merc 240D    24.4     4  147.    62  3.69  3.19  20       1     0     4     2
#>  9 Merc 230     22.8     4  141.    95  3.92  3.15  22.9     1     0     4     2
#> 10 Merc 280     19.2     6  168.   123  3.92  3.44  18.3     1     0     4     4
#> # ℹ 22 more rows

Read from a specific range

read_ods("plant.ods", sheet = "mtcars_ods", range = "A1:C10")
#> New names:
#> • `` -> `...1`
#> # A tibble: 9 × 3
#>   ...1                mpg   cyl
#>   <chr>             <dbl> <dbl>
#> 1 Mazda RX4          21       6
#> 2 Mazda RX4 Wag      21       6
#> 3 Datsun 710         22.8     4
#> 4 Hornet 4 Drive     21.4     6
#> 5 Hornet Sportabout  18.7     8
#> 6 Valiant            18.1     6
#> 7 Duster 360         14.3     8
#> 8 Merc 240D          24.4     4
#> 9 Merc 230           22.8     4

You cannot append to an existing sheet.

write_ods(iris, "plant.ods", sheet = "mtcars_ods", append = TRUE)
#> Error: Sheet mtcars_ods exists. Set update to TRUE is you want to update this sheet.

You cannot update a missing sheet.

write_ods(iris, "plant.ods", sheet = "iris", update = TRUE)
#> Error: Sheet iris does not exist. Cannot update.

Writing multiple sheets simultaneously

It is much faster to write data frames into the same file by putting them in a (named) list.

write_ods(list("iris" = iris, "plant" = PlantGrowth), "plant_multi.ods")
read_ods("plant_multi.ods", sheet = "plant")
#> # A tibble: 30 × 2
#>    weight group
#>     <dbl> <chr>
#>  1   4.17 ctrl 
#>  2   5.58 ctrl 
#>  3   5.18 ctrl 
#>  4   6.11 ctrl 
#>  5   4.5  ctrl 
#>  6   4.61 ctrl 
#>  7   5.17 ctrl 
#>  8   4.53 ctrl 
#>  9   5.33 ctrl 
#> 10   5.14 ctrl 
#> # ℹ 20 more rows

Flat ODS files (.xml or .fods)

Can be read with read_ods() 2 (note that the same function is used to read flat files, no matter the extension). This has the same behaviour and arguments as read_ods()

read_fods("plant.fods")

write_ods() can be used to write Flat ODS files

write_ods(PlantGrowth, "plant.fods")

Misc.

Use the function list_ods_sheets() to list out all sheets in an (F)ODS file.

list_ods_sheets("plant.ods")
#> [1] "Sheet1"     "mtcars_ods"

readODS 2.0.0

Starting from 2.0.0, write_ods writes NA as empty by default.

PlantGrowth2 <- tibble::as_tibble(PlantGrowth)
PlantGrowth2[1,1] <- NA
PlantGrowth2$group <- as.character(PlantGrowth2$group)

## NA is preseved; weight is still <dbl>
read_ods(write_ods(PlantGrowth2))
#> # A tibble: 30 × 2
#>    weight group
#>     <dbl> <chr>
#>  1  NA    ctrl 
#>  2   5.58 ctrl 
#>  3   5.18 ctrl 
#>  4   6.11 ctrl 
#>  5   4.5  ctrl 
#>  6   4.61 ctrl 
#>  7   5.17 ctrl 
#>  8   4.53 ctrl 
#>  9   5.33 ctrl 
#> 10   5.14 ctrl 
#> # ℹ 20 more rows

If you want NA to be written literally as the string “NA”, use na_as_string. You should literally see the string “NA” when the file is opened with LibreOffice, for example.

But the string “NA” messes up the automatic type inference of read_ods.

## NA is preseved; but weight is now <chr>
read_ods(write_ods(PlantGrowth2, na_as_string = TRUE))
#> # A tibble: 30 × 2
#>    weight group
#>    <chr>  <chr>
#>  1 NA     ctrl 
#>  2 5.58   ctrl 
#>  3 5.18   ctrl 
#>  4 6.11   ctrl 
#>  5 4.5    ctrl 
#>  6 4.61   ctrl 
#>  7 5.17   ctrl 
#>  8 4.53   ctrl 
#>  9 5.33   ctrl 
#> 10 5.14   ctrl 
#> # ℹ 20 more rows

Of course you can fix this by specifying col_types.

## NA is preseved; but weight is now <chr>
read_ods(write_ods(PlantGrowth2, na_as_string = TRUE),
         col_types = readr::cols(weight = readr::col_double()))
#> Warning: [0, 1]: expected a double, but got 'NA'
#> # A tibble: 30 × 2
#>    weight group
#>     <dbl> <chr>
#>  1  NA    ctrl 
#>  2   5.58 ctrl 
#>  3   5.18 ctrl 
#>  4   6.11 ctrl 
#>  5   4.5  ctrl 
#>  6   4.61 ctrl 
#>  7   5.17 ctrl 
#>  8   4.53 ctrl 
#>  9   5.33 ctrl 
#> 10   5.14 ctrl 
#> # ℹ 20 more rows

Several functions were removed in readODS 2.0.0. Please consider the API of readODS mature and there should not be any breaking change until readODS 3.0.0.

ods_sheets

Please use list_ods_sheets(path = "plant.ods") instead.

## ods_sheets("plant.ods")
list_ods_sheets("plant.ods")
#> [1] "Sheet1"     "mtcars_ods"

get_num_sheets_in_ods and getNrOfSheetsInODS

Please use list_ods_sheets

##get_num_sheets_in_ods("plant.ods")
length(list_ods_sheets("plant.ods"))
#> [1] 2

read.ods

Please use read_ods. In order to emulate the behaviours of read.ods, the followings are recommended

## read.ods from 1.6 to 1.8
read_ods("plant.ods", col_names = FALSE, skip = 0, na = NULL, col_types = NA, as_tibble = FALSE)
#> New names:
#> • `` -> `...1`
#> • `` -> `...2`
#>      ...1  ...2
#> 1  weight group
#> 2    4.17  ctrl
#> 3    5.58  ctrl
#> 4    5.18  ctrl
#> 5    6.11  ctrl
#> 6     4.5  ctrl
#> 7    4.61  ctrl
#> 8    5.17  ctrl
#> 9    4.53  ctrl
#> 10   5.33  ctrl
#> 11   5.14  ctrl
#> 12   4.81  trt1
#> 13   4.17  trt1
#> 14   4.41  trt1
#> 15   3.59  trt1
#> 16   5.87  trt1
#> 17   3.83  trt1
#> 18   6.03  trt1
#> 19   4.89  trt1
#> 20   4.32  trt1
#> 21   4.69  trt1
#> 22   6.31  trt2
#> 23   5.12  trt2
#> 24   5.54  trt2
#> 25    5.5  trt2
#> 26   5.37  trt2
#> 27   5.29  trt2
#> 28   4.92  trt2
#> 29   6.15  trt2
#> 30    5.8  trt2
#> 31   5.26  trt2
## read.ods older than 1.6
lapply(list_ods_sheets("plant.ods"),
       function(x) read_ods(path = "plant.ods", sheet = x, col_names = FALSE, skip = 0, na = NULL, col_types = NA, as_tibble = FALSE))
#> New names:
#> New names:
#> • `` -> `...1`
#> • `` -> `...2`
#> [[1]]
#>      ...1  ...2
#> 1  weight group
#> 2    4.17  ctrl
#> 3    5.58  ctrl
#> 4    5.18  ctrl
#> 5    6.11  ctrl
#> 6     4.5  ctrl
#> 7    4.61  ctrl
#> 8    5.17  ctrl
#> 9    4.53  ctrl
#> 10   5.33  ctrl
#> 11   5.14  ctrl
#> 12   4.81  trt1
#> 13   4.17  trt1
#> 14   4.41  trt1
#> 15   3.59  trt1
#> 16   5.87  trt1
#> 17   3.83  trt1
#> 18   6.03  trt1
#> 19   4.89  trt1
#> 20   4.32  trt1
#> 21   4.69  trt1
#> 22   6.31  trt2
#> 23   5.12  trt2
#> 24   5.54  trt2
#> 25    5.5  trt2
#> 26   5.37  trt2
#> 27   5.29  trt2
#> 28   4.92  trt2
#> 29   6.15  trt2
#> 30    5.8  trt2
#> 31   5.26  trt2
#> 
#> [[2]]
#>                   ...1 ...2 ...3  ...4 ...5 ...6  ...7  ...8 ...9 ...10 ...11
#> 1                       mpg  cyl  disp   hp drat    wt  qsec   vs    am  gear
#> 2            Mazda RX4   21    6   160  110  3.9  2.62 16.46    0     1     4
#> 3        Mazda RX4 Wag   21    6   160  110  3.9 2.875 17.02    0     1     4
#> 4           Datsun 710 22.8    4   108   93 3.85  2.32 18.61    1     1     4
#> 5       Hornet 4 Drive 21.4    6   258  110 3.08 3.215 19.44    1     0     3
#> 6    Hornet Sportabout 18.7    8   360  175 3.15  3.44 17.02    0     0     3
#> 7              Valiant 18.1    6   225  105 2.76  3.46 20.22    1     0     3
#> 8           Duster 360 14.3    8   360  245 3.21  3.57 15.84    0     0     3
#> 9            Merc 240D 24.4    4 146.7   62 3.69  3.19    20    1     0     4
#> 10            Merc 230 22.8    4 140.8   95 3.92  3.15  22.9    1     0     4
#> 11            Merc 280 19.2    6 167.6  123 3.92  3.44  18.3    1     0     4
#> 12           Merc 280C 17.8    6 167.6  123 3.92  3.44  18.9    1     0     4
#> 13          Merc 450SE 16.4    8 275.8  180 3.07  4.07  17.4    0     0     3
#> 14          Merc 450SL 17.3    8 275.8  180 3.07  3.73  17.6    0     0     3
#> 15         Merc 450SLC 15.2    8 275.8  180 3.07  3.78    18    0     0     3
#> 16  Cadillac Fleetwood 10.4    8   472  205 2.93  5.25 17.98    0     0     3
#> 17 Lincoln Continental 10.4    8   460  215    3 5.424 17.82    0     0     3
#> 18   Chrysler Imperial 14.7    8   440  230 3.23 5.345 17.42    0     0     3
#> 19            Fiat 128 32.4    4  78.7   66 4.08   2.2 19.47    1     1     4
#> 20         Honda Civic 30.4    4  75.7   52 4.93 1.615 18.52    1     1     4
#> 21      Toyota Corolla 33.9    4  71.1   65 4.22 1.835  19.9    1     1     4
#> 22       Toyota Corona 21.5    4 120.1   97  3.7 2.465 20.01    1     0     3
#> 23    Dodge Challenger 15.5    8   318  150 2.76  3.52 16.87    0     0     3
#> 24         AMC Javelin 15.2    8   304  150 3.15 3.435  17.3    0     0     3
#> 25          Camaro Z28 13.3    8   350  245 3.73  3.84 15.41    0     0     3
#> 26    Pontiac Firebird 19.2    8   400  175 3.08 3.845 17.05    0     0     3
#> 27           Fiat X1-9 27.3    4    79   66 4.08 1.935  18.9    1     1     4
#> 28       Porsche 914-2   26    4 120.3   91 4.43  2.14  16.7    0     1     5
#> 29        Lotus Europa 30.4    4  95.1  113 3.77 1.513  16.9    1     1     5
#> 30      Ford Pantera L 15.8    8   351  264 4.22  3.17  14.5    0     1     5
#> 31        Ferrari Dino 19.7    6   145  175 3.62  2.77  15.5    0     1     5
#> 32       Maserati Bora   15    8   301  335 3.54  3.57  14.6    0     1     5
#> 33          Volvo 142E 21.4    4   121  109 4.11  2.78  18.6    1     1     4
#>    ...12
#> 1   carb
#> 2      4
#> 3      4
#> 4      1
#> 5      1
#> 6      2
#> 7      1
#> 8      4
#> 9      2
#> 10     2
#> 11     4
#> 12     4
#> 13     3
#> 14     3
#> 15     3
#> 16     4
#> 17     4
#> 18     4
#> 19     1
#> 20     2
#> 21     1
#> 22     1
#> 23     2
#> 24     2
#> 25     4
#> 26     2
#> 27     1
#> 28     2
#> 29     2
#> 30     4
#> 31     6
#> 32     8
#> 33     2


  1. GESIS Leibniz-Institut für Sozialwissenschaften↩︎

  2. read_fods() and list_fods_sheets() are also available. But since version 2.2.0 read_ods() and list_ods_sheets() can determine whether the file at the path argument is flat or not.↩︎