web-dev-qa-db-ja.com

csvからRに条件を満たす行のみを読み取る方法は?

大きなCSVファイルをRに読み取ろうとしています。特定の条件を満たす行の一部のみを読み取り、操作したいです(例:Variable2 >= 3)。これは、はるかに小さなデータセットです。

データセット全体をデータフレームにロードしてから条件に応じて選択するのではなく、これらの行をデータフレームに直接読み込みたいと思います。データセット全体はメモリに簡単に収まらないからです。

20
Hernan

sqldfパッケージのread.csv.sql関数を使用し、SQL selectを使用してフィルター処理できます。 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
Karsten W.

私の本の中で最も簡単なのは、前処理を使用することです。

_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_による行全体)。

そのコマンドからの出力は、pipe()を介してRによって読み取ることができます。

これはeverythinbをRに読み込むよりも高速でメモリ効率が良くなります。

20

私は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
9
Eric

ファイルをチャンクで読み取り、各チャンクを処理してから、サブセットのみをつなぎ合わせることができます。

ファイルに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のコメントでの提案のオプション。

8
ilir

関数file(例:file("mydata.csv", open = "r"))を使用して、ファイルを読み取りモードで開くことができます。

オプション_n = 1_、l = readLines(fc, n = 1)を指定した関数readLinesを使用して、一度に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
  }

}
_
1
lucacerone