19

次のようなデータフレームがあるとします。

ID,  ID_2, FIRST, VALUE
-----------------------
'a', 'aa', TRUE, 2
'a', 'ab', FALSE, NA
'a', 'ac', FALSE, NA
'b', 'aa', TRUE, 5
'b', 'ab', FALSE, NA

したがって、VALUEはIDごとに1回だけFIRST=TRUEに設定されます。ID_2はID間で重複する可能性がありますが、重複する必要はありません。

各IDの最初の行の番号をそのIDのすべての行に入れて、VALUE列が2、2、2、5、5になるようにするにはどうすればよいですか?

forループを使用してすべてのIDを単純にループできることはわかっていますが、より効率的な方法を探しています。

4

4 に答える 4

29

質問は、ループと比較した効率を求めています。4つのソリューションの比較を次に示します。

  1. zoo::na.locf、はパッケージの依存関係を導入し、多くのエッジケースを処理しますが、「空白」の値がNAである必要があります。他のソリューションは、NA以外のブランクに簡単に適合させることができます。

  2. ベースRの単純なループ。

  3. ベースRの再帰関数。

  4. ベースRの私自身のベクトル化されたソリューション。

  5. data.framesで機能するバージョン0.3.0。の新fill()機能。tidyr

これらのソリューションのほとんどはデータフレームではなくベクトル用であるため、ID列をチェックしないことに注意してください。dplyrデータフレームがIDでグループ化されておらず、入力する値が各グループの上部にある場合は、またはでウィンドウ関数を試すことができます。data.table

# A popular solution
f1 <- zoo::na.locf

# A loop, adapted from https://stat.ethz.ch/pipermail/r-help/2008-July/169199.html
f2 <- function(x) {
  for(i in seq_along(x)[-1]) if(is.na(x[i])) x[i] <- x[i-1]
  x
}

# Recursion, also from https://stat.ethz.ch/pipermail/r-help/2008-July/169199.html
f3 <- function(z) { 
  y <- c(NA, head(z, -1))
  z <- ifelse(is.na(z), y, z)
  if (any(is.na(z))) Recall(z) else z }

# My own effort
f4 <- function(x, blank = is.na) {
  # Find the values
  if (is.function(blank)) {
    isnotblank <- !blank(x)
  } else {
    isnotblank <- x != blank
  }
  # Fill down
  x[which(isnotblank)][cumsum(isnotblank)]
}

# fill() from the `tidyr` version 0.3.0
library(tidyr)
f5 <- function(y) {
  fill(y, column)
}
# Test data, 2600 values, ~58% blanks
x <- rep(LETTERS, 100)
set.seed(2015-09-12)
x[sample(1:2600, 1500)] <- NA
x <- c("A", x) # Ensure the first element is not blank
y <- data.frame(column = x, stringsAsFactors = FALSE) # data.frame version of x for tidyr

# Check that they all work (they do)
identical(f1(x), f2(x))
identical(f1(x), f3(x))
identical(f1(x), f4(x))
identical(f1(x), f5(y)$column)

library(microbenchmark)
microbenchmark(f1(x), f2(x), f3(x), f4(x), f5(y))

結果:

Unit: microseconds
  expr      min        lq       mean    median        uq       max neval
 f1(x)  422.762  466.6355  508.57284  505.6760  527.2540   837.626   100
 f2(x) 2118.914 2206.7370 2501.04597 2312.8000 2497.2285  5377.018   100
 f3(x) 7800.509 7832.0130 8127.06761 7882.7010 8395.3725 14128.107   100
 f4(x)   52.841   58.7645   63.98657   62.1410   65.2655   104.886   100
 f5(y)  183.494  225.9380  305.21337  331.0035  350.4040   529.064   100
于 2015-09-12T07:51:06.773 に答える
25

VALUE列の値だけを繰り越す必要がある場合は、 zooパッケージのna.lofc()関数を使用できると思います。次に例を示します。

a<-c(1,NA,NA,2,NA)
na.locf(a)
[1] 1 1 1 2 2
于 2012-05-11T16:01:55.660 に答える
4

特定のIDのVALUEが常に最初のレコードに表示される場合(これはデータの場合のようです)、を使用matchしてそのレコードを見つけることができます。

df <- read.csv(textConnection("

ID,  ID_2, FIRST, VALUE
'a', 'aa', TRUE, 2
'a', 'ab', FALSE, NA
'a', 'ac', FALSE, NA
'b', 'aa', TRUE, 5
'b', 'ab', FALSE, NA

"))

df$VALUE <- df$VALUE[match(df$ID, df$ID)]
df
#    ID  ID_2  FIRST VALUE
# 1 'a'  'aa'   TRUE     2
# 2 'a'  'ab'  FALSE     2
# 3 'a'  'ac'  FALSE     2
# 4 'b'  'aa'   TRUE     5
# 5 'b'  'ab'  FALSE     5
于 2012-05-11T16:03:19.730 に答える
0

@nacnudusの+1は先頭の空白を処理します

f4 <- function(x, blank = is.na) {

  # Find the values
  if (is.function(blank)) {
    isnotblank <- !blank(x)
  } else {
    isnotblank <- x != blank
  }

  # Fill down
  xfill <- cumsum(isnotblank) 
  xfill[ xfill == 0 ] <- NA

  # Replace Blanks
  xnew <- x[ which(isnotblank) ][ xfill ]
  xnew[is.na(xnew)] <- blank
  return(xnew)
}
于 2017-01-26T10:06:42.430 に答える