Jak odczytać tylko linie, które spełniają warunek z pliku csv na R?

Próbuję odczytać duży plik csv do R. mimo, że plik jest duży, chcę pracować tylko z niektórymi wierszami, które spełniają określony warunek (np. Zmienna2 >= 3). Jest to znacznie mniejszy zbiór danych. Chciałbym odczytać te linie bezpośrednio do ramki danych, a nie załadować cały zestaw danych do ramki danych, a następnie wybrać zgodnie z warunkiem. Głównym powodem jest to, że zestaw danych nie mieści się łatwo w pamięci komputera stacjonarnego lub laptopa. Szukam rozwiązania który używa tylko R i nie wymaga Pythona ani innych języków. Dzięki.

Author: Community, 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")
 28
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.]}
 20
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

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

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(microbenchmark)

# Generate an example dataset with two numeric columns and 5 million rows
data_frame(
  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),
  check  = function(values) all(sapply(values[-1], function(x) all.equal(values[[1]], x))),
  times  = 10L
)

# Unit: seconds
#   expr       min        lq      mean    median        uq       max neval
#  readr      5.58      5.79      6.16      5.98      6.68      7.12    10
# readr2      2.94      2.98      3.07      3.03      3.06      3.43    10
#  sqldf     13.59     13.74     14.20     13.91     14.64     15.49    10
#    awk     16.83     16.86     17.07     16.92     17.29     17.77    10
#   awk2     16.86     16.91     16.99     16.92     16.97     17.57    10
 7
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
2016-12-13 01:34:11

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