Invariants for subsetting and subassignment

この vignette は、 tibble のサブセットとサブセット割り当ての不変量を定義し、その動作がデータフレームと異なる点を説明します。 目標は、動作がどのように相互作用するかを一貫して定義する小さな不変量のセットを定義することです。 いくつかのビヘイビアは,vctrsパッケージの関数,例えば,vec_slice(), vec_recycle(), vec_as_index()を用いて定義されています. それらが従う不変量の詳細については,それぞれのドキュメントを参照してください.

データフレームと tibble のサブセットとサブアサインメント演算子は特に厄介です。というのも、これらの演算子は行インデックスと列インデックスの両方をサポートしており、その両方がオプションで欠落しているからです。 これを解決するには、まず列へのアクセスを [[$ で定義し、次に列単位のサブセットを [ で定義し、次に行単位のサブセットを定義し、さらに両者の合成を定義します。

Conventions

この記事では、1つのデータフレームとそれに相当する tibble を例にして、すべての動作を説明します。:

library(tibble)
suppressWarnings(library(vctrs))
#> 
#> Attaching package: 'vctrs'
#> The following object is masked from 'package:tibble':
#> 
#>     data_frame
new_df <- function() {
  df <- data.frame(n = c(1L, NA, 3L, NA))
  df$c <- letters[5:8]
  df$li <- list(9, 10:11, 12:14, "text")
  df
}
new_tbl <- function() {
  as_tibble(new_df())
}

データフレームと tibble に対する同じコードの結果を並べて表示しています。:

結果が同一の場合(必要に応じてデータフレームに変換した後)、tibble の結果のみが表示されます。

サブセットの操作は読み取り専用です。 すべての例で同じオブジェクトが再利用されています。

df <- new_df()
tbl <- new_tbl()

また、必要に応じて、データフレームや行列を含む階層的なカラムの例も示します。:

new_tbl2 <- function() {
  tibble(
    tb = tbl,
    m = diag(4)
  )
}
new_df2 <- function() {
  df2 <- new_tbl2()
  class(df2) <- "data.frame"
  class(df2$tb) <- "data.frame"
  df2
}
df2 <- new_df2()
tbl2 <- new_tbl2()

サブセット割り当て(略してサブアサイン)のためには,テストごとにデータの新しいコピーが必要です. with_*()関数(簡潔にするためにここでは省略します)を使用すると、より簡潔な表記が可能になります。 これらの関数は、代入式を受け取り、それを新しいデータのコピーで実行し、印刷用のデータを返します。 最初の例では,実際に実行された内容が印刷され,以降の例ではこの出力は省略されています。

Column extraction

Definition of x[[j]]

x[[j]] is equal to .subset2(x, j).

注:x[[j]]は、列が存在する場合、常にサイズnrow(x)のオブジェクトを返します。

jは、.subset2(x, j)で強制されるように、1つの数値または文字列でなければなりません。

#> Warning: The `j` argument of `[[.tbl_df`
#> can't be a vector of length 2 as of
#> tibble 3.0.0.
#> Recursive subsetting is deprecated for
#> tibbles.
#> [1] NA
#> Error in .subset2(x, i, exact = exact):
#> 添え字が許される範囲外です
#> Error: Must extract column with a single
#> valid subscript.
#> x Subscript `c("n", "c")` has size 2 but
#> must be size 1.
#> Error: Must extract column with a single
#> valid subscript.
#> x Subscript `TRUE` has the wrong type
#> `logical`.
#> i It must be numeric or character.
#> Error in .subset2(x, i, exact = exact):
#> 添え字の型 'closure' が不正です
#> Error: Must extract column with a single
#> valid subscript.
#> x Subscript `mean` has the wrong type
#> `function`.
#> i It must be numeric or character.

NAインデックス、数値の境界外(OOB)値、非整数はエラーになります。

#> Error: Must extract column with a single
#> valid subscript.
#> x Subscript `NA` can't be `NA`.
#> Error: Must extract column with a single
#> valid subscript.
#> x Subscript `NA_character_` can't be
#> `NA`.
#> Error: Must extract column with a single
#> valid subscript.
#> x Subscript `NA_integer_` can't be `NA`.
#> Error in .subset2(x, i, exact = exact):
#> attempt to select more than one element
#> in get1index <real>
#> Error: Must extract column with a single
#> valid subscript.
#> x Subscript `-1` has value -1 but must
#> be a positive location.
#> Error in .subset2(x, i, exact = exact):
#> 添え字が許される範囲外です
#> Error: Can't subset columns that don't
#> exist.
#> x Location 4 doesn't exist.
#> i There are only 3 columns.
#> Error: Must extract column with a single
#> valid subscript.
#> x Can't convert from <double> to
#> <integer> due to loss of precision.
#> Error: Must extract column with a single
#> valid subscript.
#> x Can't convert from <double> to
#> <integer> due to loss of precision.

文字のOOBアクセスは、パッケージの一般的な慣例として、列がないかどうかを is.null(df[[var]]) でチェックすることがあるため silent になっています。

Definition of x$name

x$namex$"name"x[["name"]] と同一です。

データフレームとは異なり、tibble は名前を部分的にマッチさせません。 df$xはパッケージではほとんど使われないので、警告を出すことができます。

#> Warning: Unknown or uninitialised
#> column: `l`.
#> NULL
#> Warning: Unknown or uninitialised
#> column: `not_present`.
#> NULL

列のサブセット

x[j] の定義

jvec_as_index(j, ncol(x), names = names(x)) によって整数のベクトルに変換されます。 すると,x[c(j_1, j_2, ..., j_n)]は,対応する列名を保ったまま,tibble(x[[j_1]], x[[j_2]], ..., x[[j_3]])と等価になります. このことから,jは,数値または文字のベクトル,または長さ1の論理ベクトル,またはncol(x)でなければならないことになります.

繰り返されるインデックスをサブセットすると、結果として得られる列名は未定義であるため、これに依存しないでください。

カラム名が繰り返される tibble では、名前によるサブセットは最初にマッチするカラムを使用します。

nrow(df[j])nrow(df)` と同じです。

tibble は、論理行列によるインデックスをサポートしていますが、返されるベクトルのすべての値に互換性がある場合に限られます。

#> Error: Can't combine `n` <integer> and
#> `c` <character>.

x[, j]の定義

x[, j]x[j] と等しい。 tibble では、x[j]で1列になる場合は、列抽出を行いません。

x[, j, drop = TRUE] の定義

後方互換性のために、x[, j, drop = TRUE] は列の 抽出 を行い、ncol(x[j]) が1のとき、x[j][[1]] を返します。

行のサブセット

x[i, ]の定義

x[i, ]は、tibble(vec_slice(x[[1]], i), vec_slice(x[[2]], i), …)`と同じです。

つまり,i は,数値ベクトルか,長さ nrow(x) または 1 の論理ベクトルでなければなりません。 互換性のために、i は正の数を含む文字ベクトルにすることもできます。

#> Error in xj[i]: 添え字の型 'closure'
#> が不正です
#> Error: Must subset rows with a valid
#> subscript vector.
#> x Subscript `mean` has the wrong type
#> `function`.
#> i It must be logical, numeric, or
#> character.
#> Error in xj[i]: 添え字の型 'list'
#> が不正です
#> Error: Must subset rows with a valid
#> subscript vector.
#> x Subscript `list(1)` has the wrong type
#> `list`.
#> i It must be logical, numeric, or
#> character.

例外: OOB値は、エラーではなく警告を生成します。

#> Warning: The `i` argument of `[.tbl_df`
#> must lie in [0, rows] if positive, as of
#> tibble 3.0.0.
#> Use `NA_integer_` as row index to obtain
#> a row full of `NA` values.
#> # A tibble: 1 x 3
#>       n c     li    
#>   <int> <chr> <list>
#> 1    NA <NA>  <NULL>
#> Warning: The `i` argument of `[.tbl_df`
#> must use valid row names as of tibble
#> 3.0.0.
#> Use `NA_integer_` as row index to obtain
#> a row full of `NA` values.
#> # A tibble: 1 x 3
#>       n c     li    
#>   <int> <chr> <list>
#> 1    NA <NA>  <NULL>

データフレームとは異なり、長さ1の論理ベクターのみがリサイクルされます。

#> Error: Must subset rows with a valid
#> subscript vector.
#> i Logical subscripts must match the size
#> of the indexed input.
#> x Input has size 4 but subscript
#> `c(TRUE, FALSE)` has size 2.

注意:スカラー論理はリサイクルされますが、スカラー数値はリサイクルされません。 そのため、x[NA, ]x[NA_integer_, ] は異なる結果を返します。

x[i, , drop = TRUE] の定義

drop = TRUE は、一つの行を選択しない場合には効果がありません。

行と列のサブセット

x[]x[,]の定義

x[]x[,]x`と等価である。1.

x[i, j]の定義

x[i, j]x[i, ][j]と等しい。2.

x[[i, j]]の定義

i は長さ1の数値ベクトルでなければならない。 x[[i, j]]x[i, ][[j]] または vctrs::vec_slice(x[[j]], i) と同じです。

これはjが長さ1の数値または文字のベクトルでなければならないことを意味します。

注意: vec_size(x[[i, j]]) は常に1になります。 x[i, ]と異なり、x[[i, ]]は有効ではありません。

コラム更新

x[[j]] <- a の定義

a をベクトルとすると、x[[j]] <- aj 番目の列を値 a で置き換えます。

ax と同じサイズにリサイクルされるので、サイズは nrow(x) か 1 でなければなりません。 (唯一の例外は、後述するように aNULL の場合です)。 リサイクルは,リスト,データフレーム,および行列の列に対しても機能します。

#> Error in `[[<-.data.frame`(`*tmp*`,
#> "tb", value = structure(list(n = 1L, :
#> replacement has 1 row, data has 4
#> Error in `[[<-.data.frame`(`*tmp*`, "m",
#> value = structure(c(1, 0, 0, :
#> replacement has 1 row, data has 4

j はスカラーの数値または文字列でなければならず、NA にはできません。 j が OOB の場合は、右側に新しい列が追加され、必要に応じて名前が修正されます。

#> Warning in format.data.frame(if (omit)
#> x[seq_len(n0), , drop = FALSE] else
#> x, : corrupt data frame: columns will be
#> truncated or padded with NAs
#>    n c         li      V5
#> 1  1 e          9 NULL  0
#> 2 NA f     10, 11 <NA>  0
#> 3  3 g 12, 13, 14 <NA>  0
#> 4 NA h       text <NA>  0
#> Error: Can't assign column 5 in a tibble
#> with 3 columns.

df[[j]] <- a は、完全な列を置き換えるので、タイプを変更することができます。

[[<-]は、NULL を割り当てて列を削除することをサポートしています。

存在しない列を削除することはできません。

x$name <- a の定義

x$name <- ax$"name" <- a は、x[["name"]] <- a と等価である。<- a`.3.

$<- は部分一致を行いません。

Column subassignment: x[j] <- a.

a はリストまたはデータフレームです。

inherits(a, “list”)またはinherits(a, “data.frame”)TRUEならば、x[j] <- ax[[j[[1]]] <- a[[1]]と等価である。<- a[[1]],x[[j[[2]]]] <- a[[2]]`, …

length(a)が 1 のとき、jと同じ長さにリサイクルされます。

#> Warning in `[<-.data.frame`(`*tmp*`,
#> 1:2, value = list(0, 0, 0)): provided 3
#> variables to replace 2 variables
#>   n c         li
#> 1 0 0          9
#> 2 0 0     10, 11
#> 3 0 0 12, 13, 14
#> 4 0 0       text
#> Error: Can't recycle `list(0, 0, 0)`
#> (size 3) to size 2.
#> Error: Can't recycle `list(0, 0)` (size
#> 2) to size 3.

同じ列を2回更新しようとすると、エラーが発生します。

#> Error in `[<-.data.frame`(`*tmp*`, c(1,
#> 1), value = list(1, 2)): duplicate
#> subscripts for columns
#> Error: Column index 1 is used more than
#> once for assignment.

aNULLの値が含まれている場合、対応するカラムは更新後*に削除されます(つまり、位置インデックスは変更前のカラムを参照します)。

NAのインデックスはサポートされていません。

カラムの更新と同様に、[<-は既存のカラムのタイプ変更をサポートしています。

列を最後に(ギャップなしで)追加することがサポートされています。 新しい列の名前は、LHS、RHS、または名前の修復によって決定されます(優先順位はこの順です)。

#> Error in `[<-.data.frame`(`*tmp*`, 5,
#> value = list(4:1)): new columns would
#> leave holes after existing columns
#> Error: Can't assign column 5 in a tibble
#> with 3 columns.

tibble は、論理行列によるインデックスをサポートしていますが、スカラーのRHSの場合のみで、更新されるすべての列が割り当てられた値と互換性がある場合のみです。

#> Error: Subscript `is.na(tbl)` is a
#> matrix, the data `1:2` must have size 1.
#> Error: Assigned data `4` must be
#> compatible with existing data.
#> i Error occurred for column `c`.
#> x Can't convert <double> to <character>.

a は行列または配列です。

is.matrix(a) とすると、a は代入前に as.data.frame() でデータフレームに変換されます。 行が代入される場合,その行列型はすべての列と互換性がなければなりません. もし is.array(a)any(dim(a)[-1:-2] != 1) の組み合わせであれば、エラーが発生します。

#> Error: Assigned data `matrix(6:1, ncol =
#> 2)` must be compatible with existing
#> data.
#> i Error occurred for column `c`.
#> x Can't convert <integer> to
#> <character>.
#> Error: `array(8:1, dim = c(2, 1, 4))`
#> must be a vector, a bare list, a data
#> frame, a matrix, or NULL.
#> Error: `array(8:1, dim = c(4, 1, 2))`
#> must be a vector, a bare list, a data
#> frame, a matrix, or NULL.

a は別のタイプのベクトルです。

vec_is(a)とすると、x[j] <- ax[j] <- list(a) と同等です。 これは主に後方互換性のために提供されています。

行列の列を作るためには,代入前に行列をlist()でラップしなければなりません。

aNULL です。

カラム全体を削除することができます。 i を指定するとエラーになります。

#> Error in x[[jj]][iseq] <- vjj:
#> replacement (置き換え) の長さが 0 です
#> Error: `NULL` must be a vector, a bare
#> list, a data frame or a matrix.

a はベクトルではありません。

a に他の型を指定するとエラーになります。 なお、is.list(a)TRUE で、inherits(a, "list")FALSE の場合は、a はスカラーとみなされます。 詳細は ?vec_is?vec_proxy を参照してください。

#> Error in rep(value, length.out = n):
#> attempt to replicate an object of type
#> 'closure'
#> Error: `mean` must be a vector, a bare
#> list, a data frame, a matrix, or NULL.
#> Warning in `[<-.data.frame`(`*tmp*`,
#> 1, value = structure(list(coefficients
#> = c(`(Intercept)` = 37.285126167342, :
#> replacement element 2 has 32 rows to
#> replace 4 rows
#> Warning in `[<-.data.frame`(`*tmp*`,
#> 1, value = structure(list(coefficients
#> = c(`(Intercept)` = 37.285126167342, :
#> replacement element 3 has 32 rows to
#> replace 4 rows
#> Warning in `[<-.data.frame`(`*tmp*`,
#> 1, value = structure(list(coefficients
#> = c(`(Intercept)` = 37.285126167342, :
#> replacement element 5 has 32 rows to
#> replace 4 rows
#> Warning in `[<-.data.frame`(`*tmp*`,
#> 1, value = structure(list(coefficients
#> = c(`(Intercept)` = 37.285126167342, :
#> replacement element 7 has 5 rows to
#> replace 4 rows
#> Error in `[<-.data.frame`(`*tmp*`, 1,
#> value = structure(list(coefficients =
#> c(`(Intercept)` = 37.285126167342, :
#> replacement element 10 has 3 rows, need
#> 4
#> Error: `lm(mpg ~ wt, data = mtcars)`
#> must be a vector, a bare list, a data
#> frame, a matrix, or NULL.

行のサブアサイン: x[i, ] <- list(...)

x[i, ] <- a は,vec_slice(x[[j_1]], i) <- a[[1]], vec_slice(x[[j_2]], i) <- a[[2]], … .4 と同じです.

リサイクルできるのはサイズ1の値だけです。

#> Error in `[<-.data.frame`(`*tmp*`, 2:4,
#> , value = structure(list(n = c(1L, :
#> replacement element 1 has 2 rows, need 3
#> Error: Assigned data `tbl[1:2, ]` must
#> be compatible with row subscript `2:4`.
#> x 3 rows must be assigned.
#> x Element 1 of assigned data has 2 rows.
#> i Only vectors of size 1 are recycled.

互換性のため、行数を超えてインデックスを作成する場合は警告のみが表示されます。 既存のデータの最後に行を追加することは、警告なしでサポートされています。

#> Error: Can't assign row 6 in a tibble
#> with 4 rows.
#> Error: Can't negate rows that don't
#> exist.
#> x Location 5 doesn't exist.
#> i There are only 4 rows.
#> Error: Can't negate rows that don't
#> exist.
#> x Locations 5, 6, and 7 don't exist.
#> i There are only 4 rows.
#> Error: Can't negate rows that don't
#> exist.
#> x Location 6 doesn't exist.
#> i There are only 4 rows.

互換性のために、iは正の数を含む文字ベクトルにすることもできます。

行と列のサブアサインメント

x[i, j] <- a の定義

x[i, j] <- ax[i, ][j] <- a と同等である。5: x[i, j] はサブセットに対して対称であり、列のサブアサインに対して対称である。

x[i, j]へのサブアサインは、データフレームよりもTibblesの方が厳しい。 x[i, j] <- a は、既存のカラムのデータタイプを変更することはできません。

#> Error: Assigned data `tbl[1:2, 2]` must
#> be compatible with existing data.
#> i Error occurred for column `n`.
#> x Can't convert <character> to
#> <integer>.
#> Warning in `[<-.data.frame`(`*tmp*`,
#> 2:3, 2, value = list(9, 10:11)):
#> provided 2 variables to replace 1
#> variables
#>    n c         li
#> 1  1 e          9
#> 2 NA 9     10, 11
#> 3  3 9 12, 13, 14
#> 4 NA h       text
#> Error: Assigned data `tbl[1:2, 3]` must
#> be compatible with existing data.
#> i Error occurred for column `c`.
#> x Can't convert <list> to <character>.
#> Warning in `[<-.data.frame`(`*tmp*`,
#> 2:3, 3, value = structure(list(n =
#> c(1L, : provided 3 variables to replace
#> 1 variables
#>    n c   li
#> 1  1 e    9
#> 2 NA f    1
#> 3  3 g   NA
#> 4 NA h text
#> Error: Internal error in
#> `df_cast_opts()`: Data frame must have
#> names.
#> Warning in matrix(value, n, p): データ長
#> [8] が列数 [3] を整数で割った、もしくは
#> 掛けた値ではありません
#>   tb.n tb.c tb.li m.1 m.2 m.3 m.4
#> 1    1    e     9   1   0   0   0
#> 2    1    0     0   0   1   0   0
#> 3    0    1     0   0   0   1   0
#> 4   NA    h  text   0   0   0   1
#> Error: Assigned data `tbl2[1:2, 2]` must
#> be compatible with existing data.
#> i Error occurred for column `tb`.
#> x Can't convert <double[,4]> to
#> <tbl_df>.

つまり、NA(これは論理)で初期化されたカラムには、後から別の型の値を入れることはできません。 カラムの初期化には正しい型の NA を使用してください。

#> Error: Assigned data `3:2` must be
#> compatible with existing data.
#> i Error occurred for column `x`.
#> x Can't convert from <integer> to
#> <logical> due to loss of precision.
#> * Locations: 1, 2.

新しい列に対しては、x[i, j] <- a で、割り当てられていない行を NA で埋めます。

#> Error in x[[jj]][iseq] <- vjj:
#> replacement (置き換え) の長さが 0 です
#> Error: `NULL` must be a vector, a bare
#> list, a data frame or a matrix.

同様に、新しい行に対しては、x[i, j] <- aとすることで、割り当てられていない列をNAで埋めることができます。

x[[i, j]] <- a の定義

i は長さ1の数値ベクトルでなければならない。 x[[i, j]] <- ax[i, ][[j]] と同じです。<- a.6.

注: vec_size(a) は 1 にしてください。 x[i, ] <- とは異なり、x[[i, ]] <- は有効ではありません。


  1. x[,j]x[j]と同じなので、x[,]x[,j]と同じです。

  2. x[i, j]のより効率的な実装は、x[j][i, ]`にフォワードします。

  3. $ はサブセットとサブアサインを比較するときに [[ とほぼ完全に対称的な動作をします。

  4. x[i, ]はサブセットとサブアサインに対して対称性があります。

  5. x[i, j]はサブセットとサブアサインに対して対称的です。 x[i, j] <- a のより効率的な実装では、x[j][i, ] <- a に進みます。

  6. x[[i, j]]はサブセットとサブアサインに対して対称性があります。 効率的な実装では、ij がスカラーであることをチェックして、x[i, j][[1]] <- a に進みます。