Как читать только строки, которые удовлетворяют условию из 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
}
}