2017-10-04 5 views
4

私はRで大きなデータフレームを持っており、既存の列に基づいていくつかの新しい列を作成したいと思います。ただし、各行について、新しい値は他の行にも依存します。ここで従属サブセットの大きなデータフレームの各行の複数の新しい集計

は私が何をしたいのか、いくつかのダミーデータ

colnames <- c('date', 'docnr', 'clientid', 'values') 
docnr <- c(1,2,3,4,5,6) 
dates <- c('2017-01-01', '2017-02-01', '2017-03-01', '2017-04-01','2017-01-05', '2017-02-05') 
clients <- c(1,1,1,1,2,2) 
values <- c(10,14,4,7,9,19) 
df <- data.frame(cbind(dates, docnr, clients, values)) 
names(df) <- colnames 
df$date <- as.Date(df$date, format = "%Y-%m-%d") 

df 
     date docnr clientid values 
1 2017-01-01  1  1  10 
2 2017-02-01  2  1  14 
3 2017-03-01  3  1  4 
4 2017-04-01  4  1  7 
5 2017-01-05  5  2  9 
6 2017-02-05  6  2  19 

です(一意docnrによって識別される)すべての行は、日付とクライアントIDを取り、持っている他のすべての行を探して、あります同じクライアントID、およびより早い日付。

次に、このサブセットからいくつかのものを計算したいと思います。たとえば、このサブセットの合計行数と、このサブセットのすべての値の合計が必要です。

したがって、この例のデータのために、私が期待する:

 date docnr clientid values counts totals 
1 2017-01-01  1  1  10  0  0 
2 2017-02-01  2  1  14  1  10 
3 2017-03-01  3  1  4  2  24 
4 2017-04-01  4  1  7  3  28 
5 2017-01-05  5  2  9  0  0 
6 2017-02-05  6  2  19  1  9 

瞬間、私はforループを使用します。

counts <- numeric(0) 
totals <- numeric(0) 
for (i in 1:nrow(df)) { 
    tmp <- df[df$date< df$date[i] & df$clientid== df$clientid[i], 
       c("date", "docnr","value")] 
    cnt <- nrow(tmp) 
    tot <- sum(tmp$value) 
    counts[i] <- res 
    totals[i] <- tot 
} 
df$counts <- counts 
df$totals <- totals 

このループは、700Kの行のデータフレームのために明らかに非常に遅いです(まだ完了までにそれを実行していない)。 doSNOWを使った並列実装は、はるかに優れた縮尺ではないようです。

sqldfのSQLクエリを使用しようとしましたが、サブクエリは一度に1つの値しか返せません。これは、定義したいすべての新しい列に対してクエリを実行することを意味します(さらに、後の派生列)。

私はSQLオブジェクト(Is it possible to get multiple values from a subquery?)で解決策を見つけましたが、オブジェクトはRのsqldfでは機能しませんでした。 2番目のクエリは最初のクエリの情報を持つ必要があるため、結合の使用は機能しません。

私はRで始まったばかりです(SQLにもあまりよく慣れていません)ので、これを行うより効率的な方法を誰かが知っていれば、私は非常に義務づけられます。

+0

をしたいデータが含まれていますすべての要素を文字に強制するので、.frame'を使用します。代わりに 'data.frame'を直接使用してください:' df lmo

答えて

4

ここでは、グループ化にaveを使用する2つのベースRコードがあります。

# get counts 
df$counts <- ave(df$docnr, df$clientid, FUN=seq_along) - 1L 
# get lagged cumulative sum 
df$totals <- ave(df$values, df$clientid, FUN=function(x) c(0, head(cumsum(x), -1))) 

これは、私は上記のコードは、あなたが記述されたデータのために十分迅速に行うウィルと思われる

df 
     date docnr clientid values counts totals 
1 2017-01-01  1  1  10  0  0 
2 2017-02-01  2  1  14  1  10 
3 2017-03-01  3  1  4  2  24 
4 2017-04-01  4  1  7  3  28 
5 2017-01-05  5  2  9  0  0 
6 2017-02-05  6  2  19  1  9 

を返します。ただし、数十億行のデータを扱う場合は、data.tableをお勧めします。 data.tableに上記のコードをアナログseq_len(.N)seq_alongshiftの役割を満たす

library(data.table) 
setDT(df)[, c("counts", "totals") := .(seq_len(.N) - 1L, shift(cumsum(values), fill=0)), 
      by=clientid] 

は、前のコードでc(0, head(cumsum(x), -1))の役割を満たすであろう。

これは、上記と同じ値を持つdata.tableを返します。

df 
     date docnr clientid values counts totals 
1: 2017-01-01  1  1  10  0  0 
2: 2017-02-01  2  1  14  1  10 
3: 2017-03-01  3  1  4  2  24 
4: 2017-04-01  4  1  7  3  28 
5: 2017-01-05  5  2  9  0  0 
6: 2017-02-05  6  2  19  1  9 

データ

df <- 
structure(list(date = structure(c(17167, 17198, 17226, 17257, 
17171, 17202), class = "Date"), docnr = c(1, 2, 3, 4, 5, 6), 
    clientid = c(1, 1, 1, 1, 2, 2), values = c(10, 14, 4, 7, 
    9, 19)), .Names = c("date", "docnr", "clientid", "values" 
), row.names = c(NA, -6L), class = "data.frame") 
+0

ありがとう!新しい派生カラムの数が2以上であれば、私にとって最速の解決策は 'data.table'を使う方法です。 3.5秒で700k行を実行しました。新しい列が1つの場合、 'ave'の方が高速です。 – Desolane

1

これは今dplyr

This works 

df$values <- as.numeric(as.character(df$values)) 
df1 <- df %>% 
    arrange(clientid, date) %>% 
    group_by(clientid) %>% 
    mutate(counts = row_number()-1, 
     total = lag(cumsum(values),k=1, default=0)) %>% 
    ungroup() 

DF1で簡単に動作しますが、あなたが `データの内` cbind`を使用しないでください

+0

実際に動作し、私は読めるコードが好きです。速いですが、@ lmoの他の答えほど高速ではありません。 700k行では、この解決策は私のためには23秒、 'setDT'を使うと3.5秒かかります。 – Desolane

関連する問題