Как читать только строки, которые удовлетворяют условию из CSV в R?

Я пытаюсь прочитать большой CSV файл в R. Я хочу читать и работать только с некоторыми строками, которые удовлетворяют определенному условию (например, Variable2 >= 3). Это гораздо меньший набор данных.

Я хочу прочитать эти строки непосредственно в кадр данных, а не загружать весь набор данных в кадр данных и затем выбирать в соответствии с условием, поскольку весь набор данных не легко помещается в память.

Ответы

Ответ 1

Вы можете использовать функцию read.csv.sql в пакете sqldf и фильтровать с помощью выбора SQL. На странице справки 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")

Ответ 2

Самым простым (в моей книге) является использование предварительной обработки.

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> 

Здесь мы используем awk. Мы говорим awk использовать запятую в качестве разделителя полей, а затем использовать условие, если первое поле больше 20, чтобы решить, будем ли мы печатать (вся строка через $0).

Выход из этой команды может быть прочитан R через pipe().

Это будет быстрее и эффективнее с точки зрения памяти, чем чтение everythinb в R.

Ответ 3

Я смотрел в readr::read_csv_chunked, когда увидел этот вопрос и подумал, что сделаю некоторый бенчмаркинг. В этом примере read_csv_chunked работает хорошо, и увеличение размера куска было полезным. sqldf был только немного быстрее, чем 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

Ответ 4

Вы можете прочитать файл в кусках, обработать каждый кусок, а затем сшить только подмножества.

Вот минимальный пример, предполагающий, что файл имеет 1001 (включая заголовок), и только 100 будет вписываться в память. Данные имеют 3 столбца, и мы ожидаем, что не более 150 строк удовлетворяют условию (это необходимо для предварительного выделения пространства для окончательных данных:

# 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

Изменить: Добавлена ​​опция stringsAsFactors=FALSE в предложении @lucacerone в комментариях.

Ответ 5

Вы можете открыть файл в режиме чтения с помощью функции file (например, file("mydata.csv", open = "r")).

Вы можете прочитать файл по одной строке за раз, используя функцию readLines с опцией n = 1, l = readLines(fc, n = 1).

Затем вам нужно проанализировать свою строку, используя функцию, например strsplit, регулярные выражения, или вы можете попробовать пакет stringr (доступный из CRAN).

Если строка выполнила условия для импорта данных, вы импортируете их.

Подводя итог, я бы сделал что-то вроде этого:

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
  }

}