Jak odczytać tylko linie spełniające warunek z pliku csv na R?

Próbuję odczytać duży plik csv do R. chcę tylko czytać i pracować z niektórymi wierszami, które spełniają określony warunek (np. Variable2 >= 3). Jest to znacznie mniejszy zbiór danych.

Chcę odczytać te linie bezpośrednio do ramki danych, zamiast załadować cały zestaw danych do ramki danych, a następnie wybrać zgodnie z warunkiem, ponieważ cały zestaw danych nie mieści się łatwo w pamięci.

Author: smci, 2014-04-21

5 answers

Możesz użyć funkcji read.csv.sql w pakiecie sqldf i filtrować za pomocą SQL select. Ze strony pomocy read.csv.sql:

library(sqldf)
write.csv(iris, "iris.csv", quote = FALSE, row.names = FALSE)
iris2 <- read.csv.sql("iris.csv", 
    sql = "select * from file where `Sepal.Length` > 5", eol = "\n")
 32
Author: Karsten W.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-11 14:20:29

Zdecydowanie najprostszym (w mojej książce) jest użycie wstępnego przetwarzania.

R> DF <- data.frame(n=1:26, l=LETTERS)
R> write.csv(DF, file="/tmp/data.csv", row.names=FALSE)
R> read.csv(pipe("awk 'BEGIN {FS=\",\"} {if ($1 > 20) print $0}' /tmp/data.csv"),
+           header=FALSE)
  V1 V2
1 21  U
2 22  V
3 23  W
4 24  X
5 25  Y
6 26  Z
R> 

Tutaj używamy awk. Mówimy awk, aby użyć przecinka jako separatora pola, a następnie użyć conditon 'jeśli pierwsze pole większe niż 20', aby zdecydować, czy drukujemy (cały wiersz przez $0).

Wyjście z tego polecenia może być odczytane przez r poprzez pipe().

To będzie szybsze i bardziej wydajne niż odczyt everythinb do R.]}
 23
Author: Dirk Eddelbuettel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-21 13:16:25

Patrzyłem na readr::read_csv_chunked Kiedy zobaczyłem to pytanie i pomyślałem, że zrobię kilka benchmarkingu. W tym przykładzie, read_csv_chunked robi dobrze i zwiększenie rozmiaru kawałka było korzystne. sqldf był tylko nieznacznie szybszy niż awk.

library(tidyverse)
library(sqldf)
library(data.table)
library(microbenchmark)

# Generate an example dataset with two numeric columns and 5 million rows
tibble(
  norm = rnorm(5e6, mean = 5000, sd = 1000),
  unif = runif(5e6, min = 0, max = 10000)
) %>%
  write_csv('medium.csv')

microbenchmark(
  readr  = read_csv_chunked('medium.csv', callback = DataFrameCallback$new(function(x, pos) subset(x, unif > 9000)), col_types = 'dd', progress = F),
  readr2 = read_csv_chunked('medium.csv', callback = DataFrameCallback$new(function(x, pos) subset(x, unif > 9000)), col_types = 'dd', progress = F, chunk_size = 1000000),
  sqldf  = read.csv.sql('medium.csv', sql = 'select * from file where unif > 9000', eol = '\n'),
  awk    = read.csv(pipe("awk 'BEGIN {FS=\",\"} {if ($2 > 9000) print $0}' medium.csv")),
  awk2   = read_csv(pipe("awk 'BEGIN {FS=\",\"} {if ($2 > 9000) print $0}' medium.csv"), col_types = 'dd', progress = F),
  fread  = fread(cmd = "awk 'BEGIN {FS=\",\"} {if ($2 > 9000) print $0}' medium.csv"),
  check  = function(values) all(sapply(values[-1], function(x) all.equal(values[[1]], x))),
  times  = 10L
)

# Updated 2020-05-29

# Unit: seconds
#   expr   min    lq  mean  median    uq   max neval
#  readr   2.6   2.7   3.1     3.1   3.5   4.0    10
# readr2   2.3   2.3   2.4     2.4   2.6   2.7    10
#  sqldf  14.1  14.1  14.7    14.3  15.2  16.0    10
#    awk  18.2  18.3  18.7    18.5  19.3  19.6    10
#   awk2  18.1  18.2  18.6    18.4  19.1  19.4    10
#  fread  17.9  18.0  18.2    18.1  18.2  18.8    10

# R version 3.6.2 (2019-12-12)
# macOS Mojave 10.14.6        

# data.table 1.12.8
# readr      1.3.1 
# sqldf      0.4-11
 13
Author: Eric,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-30 04:55:59

Możesz odczytać plik w kawałkach, przetworzyć każdy kawałek, a następnie połączyć tylko podzbiory razem.

Oto minimalny przykład, zakładając, że plik ma 1001 (włącznie. nagłówka) linii i tylko 100 zmieści się w pamięci. Dane mają 3 kolumny i oczekujemy, że co najwyżej 150 wierszy spełni warunek (jest to potrzebne do wstępnego przydzielenia przestrzeni dla ostatecznych danych:

# initialize empty data.frame (150 x 3)
max.rows <- 150
final.df <- data.frame(Variable1=rep(NA, max.rows=150), 
                       Variable2=NA,  
                       Variable3=NA)

# read the first chunk outside the loop
temp <- read.csv('big_file.csv', nrows=100, stringsAsFactors=FALSE)
temp <- temp[temp$Variable2 >= 3, ]  ## subset to useful columns
final.df[1:nrow(temp), ] <- temp     ## add to the data
last.row = nrow(temp)                ## keep track of row index, incl. header

for (i in 1:9){    ## nine chunks remaining to be read
  temp <- read.csv('big_file.csv', skip=i*100+1, nrow=100, header=FALSE,
                   stringsAsFactors=FALSE)
  temp <- temp[temp$Variable2 >= 3, ]
  final.df[(last.row+1):(last.row+nrow(temp)), ] <- temp
  last.row <- last.row + nrow(temp)    ## increment the current count
}

final.df <- final.df[1:last.row, ]   ## only keep filled rows
rm(temp)    ## remove last chunk to free memory

Edit: Dodano opcję stringsAsFactors=FALSE na sugestię @ lucacerone w komentarzach.

 8
Author: ilir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-21 13:43:29

Plik można otworzyć w trybie odczytu za pomocą funkcji file (np. file("mydata.csv", open = "r")).

Możesz odczytać plik po jednej linii na raz za pomocą funkcji readLines z opcją n = 1, l = readLines(fc, n = 1).

Następnie musisz przetworzyć swój łańcuch za pomocą funkcji, takich jak strsplit, wyrażenia regularne, lub możesz wypróbować pakiet stringr (dostępny od CRAN).

Jeśli linia spełnia warunki importowania danych, importujesz ją.

Podsumowując, zrobiłbym coś takiego:

df = data.frame(var1=character(), var2=int(), stringsAsFactors = FALSE)
fc = file("myfile.csv", open = "r")

i = 0
while(length( (l <- readLines(fc, n = 1) ) > 0 )){ # note the parenthesis surrounding l <- readLines..

   ##parse l here: and check whether you need to import the data.

   if (need_to_add_data){
     i=i+1
     df[i,] = #list of data to import
  }

}
 1
Author: lucacerone,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-21 12:46:21