Repository navigation
na.strings is too literal when column is quoted on file #2586
Description
Activity
- added a commit that references this issue
on Mar 3, 2018 Please check that documentation edit makes sense. If so, maybe quotes should be disallowed from inside
na.stringsvalues. You shouldn't really be able to do what you did above! Is there a real use-case that needs quoted NA values to be recognized? Maybe if the separator is in the NA value or something crazy like that? (Ifsepis present inna.strings=that should be disallowed too, thinking about it.)Yes, this came about from a real use-case: example was just meant to be minimal.
For example, in the attached file,
Age not reportwas intended to be a missing value:library(magrittr) na <- "Age not report" fread("Age-not-report-small.txt", na.strings = na, sep = ",") %$% anyNA(age_5y) #> [1] FALSE fread("Age-not-report-small.txt", na.strings = na, sep = ",") %$% any(age_5y == na) #> [1] TRUE
Strangely, though
readr::read_csvdoes get it right, it takes a long time to read in:system.time(readr::read_csv("Age-not-report-small.txt", na = "Age not report")) Parsed with column specification: cols( age_5y = col_character() ) user system elapsed 1.15 0.00 1.15Ok thanks. I think I get it now then. So it's when the source system uses some special string in a string column to represent NA, and then quotes every field when writing the csv. Hence the quotes around the special string.
Reacted by HughParsonage, Michael Chirico, Maxim Nazarov and acvill-kanvasJust came across the same:
fread('a,b\n1,"-"', na.strings = '-') # a b # 1: 1 - # compared to fread('a,b\n1,"-"', na.strings = '"-"') # a b # 1: 1 NAIMO they should return the same output -- I don't think I should need to keep track of the quoting rule used in my file unless absolutely necessary. Here,
freadcorrectly determines that field is surrounded by"and understands the contents of the field to be between"", sona.stringsshould apply to the content of the field.@mattdowle exactly right, and I think it's quite common for CSV writers to just quote everything by default. My use case has
- / -as theNAstring...@MichaelChirico My understanding is that current behavior is not an omission: it was implemented specifically with the intention to disambiguate NA strings versus "NA" strings. For example, in a file like this
key,value 1,"orange" 2,"" 3, 4,"apple"the second row is an empty string, while 3rd row is an NA string.
Similarly, in this file (which must be parsed with
na_strings="NA"):index,state 93456,"CA" 72001,"NA" 14829,NAthe second row has 2-character string
"NA", whereas the 3rd row contains a missing (NA) string.
Now of course your use case is just as valid as the ones presented here. I'm just pointing out that the current behavior is not a clear-cut "bug": it is the way it is by design.
The real question then is whether the current design is a good one, or where do we go from here?
- The simplest approach is not to do anything: declare the current behavior correct and close the issue;
- Another approach would be to treat both
"NA"andNAas an NA-string, as the OP suggests; - Another possibility is to treat
"NA"as an NA-string only if there are no unquotedNAs in the same column, however if there are then"NA"becomes a 2-character string; - Or perhaps we treat both
"NA"andNAas 2-character strings (not NAs) regardless of thena_stringssetting -- this way a character column produced by fread will never contain an NA value. - any other?
The important use-case to consider is that of an empty string (which is the default
na_stringssetting). In a file like this:A,B,C 1,foo,2 3,,4 ,bar,5should we consider the column
Bto contain["foo", "", "bar"]or["foo", NA, "bar"]. In my view the first is a more reasonable interpretation, but of course opinions may vary...I agree that there is not a straightforward fix. However, my view is that when every entry in a column is quoted, the
na.stringsargument does not need to be. For exampleA,"B" 1,"y", 2,"x", 3,"NA"The last value should be regarded as missing if
na.strings = "NA". So this fits in with your third bullet point (though I'm actually advocating for a more narrow change: only if all the values are quoted should `"NA" be treated as an NA-string).I do think that if
na.stringscontains a stringsthens %in% vshould beFALSEfor allv, and if the parser cannot honour this, there should be a warning. For me at least,freadis far better than a text editor to view files, and so I use it to modifyna.stringsas required, using the values as parsed. In the file that motivated this issue, the string"Age not report"was undocumented and occurred about 70 million rows down and 120 columns across, so the only plausible way I was ever going to detect it was to usefreadanduniqueon the column. But sincefreadhad stripped away the quotes, I sawAge not reportrather than"Age not report".- Agree w Hugh here. we shouldn't close the door to meticulous handling of more ambiguous cases but my feeling is cases like mine will be far more common. Especially in the special set of cases when a column is fully properly quoted for a given field on _every_ row, where i think it's easy to agree about the right behavior…On Thu, May 3, 2018, 1:01 AM HughParsonage ***@***.***> wrote: I agree that there is not a straightforward fix. However, my view is that when every entry in a column is quoted, the na.strings argument does not need to be. For example A,"B" 1,"y", 2,"x", 3,"NA" The last value should be regarded as missing if na.strings = "NA". So this fits in with your third bullet point (though I'm actually advocating for a more narrow change: only if *all* the values are quoted should `"NA" be treated as an NA-string). I do think that if na.strings contains a string s then s %in% v should be FALSE for all v, and if the parser cannot honour this, there should be a warning. For me at least, fread is far better than a text editor to view files, and so I use it to modify na.strings as required, using the values as parsed. In the file that motivated this issue, the string "Age not report" was undocumented and occurred about 70 million rows down and 120 columns across, so the only plausible way I was ever going to detect it was to use fread and unique on the column. But since fread had stripped away the quotes, I saw Age not report rather than "Age not report". — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#2586 (comment)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/AHQQdWcpmZfG0f5_hYA1FyBynZX86hg5ks5tueZ4gaJpZM4RqaXA> .
So suppose we have the following 4 test files:
file1.txt: file2.txt: file3.txt: file4.txt: i,value i,"value" i,value i,value 1,foo 1,"foo" 1,"foo" 1,"foo" 2,bar 2,"bar" 2,"bar" 2,"bar" 3, 3,"" 3, 3,"" 4,baz 4,"baz" 4,"baz" 4,bazWhat do you think is the ideal way to read each of these files with each of the following commands?
fread("fileN.txt", na.strings="") fread("fileN.txt", na.strings="baz") fread("fileN.txt")Once we have an agreement on what the "right" way is, we can try to figure out what logic can implement it.
6 remaining items
Something else to consider is that with the
pythonlibrarypandas, writing to a csv file with thequoting=csv.QUOTE_NONNUMERICwill always write""in the field for missing values (even for numeric columns).I don't believe the
python/pandasway is "correct" butdata.table 1.10.4-3is able to handle it by specifyingna.strings = c('', '""'), whiledata.table 1.11.2is not. For small files, manually looping over the character columns and replacing""withNAisn't too much of a hassle, but for people who switch betweenpythonandRa lot, being able to read in files written withpandaswould be very helpful.So recreating this table:
library(data.table) df <- data.table(i = c(1.0, NA, 3.0, 4.0), value = c("foo", "bar", NA, "baz")) df # i value # 1: 1 foo # 2: NA bar # 3: 3 <NA> # 4: 4 baz str(df) # Classes ‘data.table’ and 'data.frame': 4 obs. of 2 variables: # $ i : num 1 NA 3 4 # $ value: chr "foo" "bar" NA "baz" # - attr(*, ".internal.selfref")=<externalptr>
Using the following python code:
import pandas as pd import csv df = pd.DataFrame({'i': [1.0, None, 3.0, 4.0], 'value': ['foo', 'bar', None, 'baz']}) print(df) # i value # 0 1.0 foo # 1 NaN bar # 2 3.0 None # 3 4.0 baz
And writing it to a txt file with:
df.to_csv('file.txt', index=False, quoting=csv.QUOTE_NONNUMERIC)
Results in this file:
"i","value" 1.0,"foo" "","bar" 3.0,"" 4.0,"baz"With
data.table 1.10.4-3I am able to read it back in to match the originalRtable usingna.strings = c('', '""'):library(data.table) df <- fread('"i","value"\n1.0,"foo"\n"","bar"\n3.0,""\n4.0,"baz"', na.strings = c('', '""')) df # i value # 1: 1 foo # 2: NA bar # 3: 3 NA # 4: 4 baz str(df) # Classes ‘data.table’ and 'data.frame': 4 obs. of 2 variables: # $ i : num 1 NA 3 4 # $ value: chr "foo" "bar" NA "baz" # - attr(*, ".internal.selfref")=<externalptr>
While using
na.strings = c('')withdata.table 1.10.4-3results in the column being read in as a character:library(data.table) df <- fread('"i","value"\n1.0,"foo"\n"","bar"\n3.0,""\n4.0,"baz"', na.strings = c('')) df # i value # 1: 1.0 foo # 2: NA bar # 3: 3.0 NA # 4: 4.0 baz str(df) # Classes ‘data.table’ and 'data.frame': 4 obs. of 2 variables: # $ i : chr "1.0" NA "3.0" "4.0" # $ value: chr "foo" "bar" NA "baz" # - attr(*, ".internal.selfref")=<externalptr>
With
data.table 1.11.2, there isn't a way to read the file in to match the original table. It always treatsias numeric and leaves the empty string invalue:library(data.table) df1 <- fread('"i","value"\n1.0,"foo"\n"","bar"\n3.0,""\n4.0,"baz"', na.strings = c('')) df2 <- fread('"i","value"\n1.0,"foo"\n"","bar"\n3.0,""\n4.0,"baz"', na.strings = c('', '""')) df3 <- fread('"i","value"\n1.0,"foo"\n"","bar"\n3.0,""\n4.0,"baz"', na.strings = c('""')) identical(df1, df2) # TRUE identical(df1, df3) # TRUE df1 # i value # 1: 1 foo # 2: NA bar # 3: 3 # 4: 4 baz str(df1) # Classes ‘data.table’ and 'data.frame': 4 obs. of 2 variables: # $ i : num 1 NA 3 4 # $ value: chr "foo" "bar" "" "baz" # - attr(*, ".internal.selfref")=<externalptr>
Reacted by LenticularI am continuing to have problems with quoted NA strings in version 1.12.2 where the same quoted NA string ("-" in my case) is differentially treated depending on whether the field is read as integer, numeric, or character.
Consider the following example:
dt1 <- fread('"A","B","C"\n"1","b","1.2"\n"-","-","-"', na.strings="-") dt1 # A B C # 1: 1 b 1.2 # 2: - - - dt2 <- fread('"A","B","C"\n"1","b","1.2"\n"-","-","-"', na.strings='"-"') dt2 # A B C # 1: 1 b 1.2 # 2: NA - NA sapply(dt2, class) # A B C # "integer" "character" "numeric"Is this expected behavior?
If so, how can I circumvent this behavior to have all "-" recognized as NA irrespective of the class of the field?
To give an idea of the context of this issue, I am working with a lab manifest that has a mix of sample IDs, comments, and measurements which are all reported in a fully quoted csv. Running sed or another command line tool is not an option to replace the "-" prior to fread since some of the sample IDs contain the "-" character.
Yes, this came about from a real use-case: example was just meant to be minimal.
For example, in the attached file,
Age not reportwas intended to be a missing value:library(magrittr) na <- "Age not report" fread("Age-not-report-small.txt", na.strings = na, sep = ",") %$% anyNA(age_5y) #> [1] FALSE fread("Age-not-report-small.txt", na.strings = na, sep = ",") %$% any(age_5y == na) #> [1] TRUE
Hey, @HughParsonage , did you get any solution for this case?
Thanks in advanceCame to report this same issue in a real use case. Here "no se midió" should be the NA value, but because columns are quoted it doesn't work as expected.
read.csvdoes work.url <- "https://ciam.ambiente.gob.ar/dt_csv.php?dt_id=372" data.table::fread(url, sep = ";", na.strings = "no se midió") |> _$escher_coli_nmp_100ml |> head() #> [1] "no se midió" "no se midió" "no se midió" "no se midió" "no se midió" #> [6] "no se midió" data.table::fread(url, sep = ";", na.strings = '"no se midió"') |> _$escher_coli_nmp_100ml |> head() #> [1] NA NA NA NA NA NA read.csv(url, sep = ";", na.strings = "no se midió") |> _$escher_coli_nmp_100ml |> head() #> [1] NA NA NA NA NA NA
Created on 2023-12-08 with reprex v2.0.2
Reacted by Jan Gorecki and Toby Dylan Hocking
#Min reprexUsing
na_string <- '"x y z"'will get the right answer, but that was a bit difficult to deduce.Output of verbose output:
data.tableversion:#Output of sessionInfo()