<- c(2.1, 4.2, 3.3, 5.4) x
4 Subconjunto
4.1 Introducción
Los operadores de subconjuntos de R son rápidos y potentes. Dominarlos le permite realizar operaciones complejas de manera sucinta de una manera que pocos otros idiomas pueden igualar. La creación de subconjuntos en R es fácil de aprender pero difícil de dominar porque necesita interiorizar una serie de conceptos interrelacionados:
Hay seis formas de crear subconjuntos de vectores atómicos.
Hay tres operadores de subconjuntos,
[[
,[
y$
.Los operadores de creación de subconjuntos interactúan de manera diferente con diferentes tipos de vectores (por ejemplo, vectores atómicos, listas, factores, matrices y data frames).
La creación de subconjuntos se puede combinar con la asignación.
La creación de subconjuntos es un complemento natural de str()
. Mientras que str()
le muestra todas las piezas de cualquier objeto (su estructura), la creación de subconjuntos le permite extraer las piezas que le interesan. Para objetos grandes y complejos, recomiendo usar el RStudio Viewer interactivo, que puedes activar con View(my_object)
.
Prueba
Responda este breve cuestionario para determinar si necesita leer este capítulo. Si las respuestas le vienen a la mente rápidamente, puede saltarse cómodamente este capítulo. Comprueba tus respuestas en la Sección 4.6.
¿Cuál es el resultado de subdividir un vector con enteros positivos, enteros negativos, un vector lógico o un vector de caracteres?
¿Cuál es la diferencia entre
[
,[[
y$
cuando se aplica a una lista?¿Cuándo debería usar
drop = FALSE
?Si
x
es una matriz, ¿qué hacex[] <- 0
? ¿En qué se diferencia dex <- 0
?¿Cómo puede usar un vector con nombre para volver a etiquetar variables categóricas?
Estructura
La Sección 4.2 comienza enseñándote acerca de
[
. Aprenderá las seis formas de crear subconjuntos de vectores atómicos. Luego aprenderá cómo actúan esas seis formas cuando se usan para crear subconjuntos de listas, matrices y data frames.La Sección 4.3 amplía su conocimiento de los operadores de subconjuntos para incluir
[[
y$
y se centra en los principios importantes de simplificar frente a preservar.La Sección 4.4 aprenderá el arte de la subasignación, que combina subconjuntos y asignación para modificar partes de un objeto.
La Sección 4.5 llo guía a través de ocho aplicaciones importantes, pero no obvias, de subconjuntos para resolver problemas que a menudo encuentra en el análisis de datos.
4.2 Selección de varios elementos
Utilice [
para seleccionar cualquier número de elementos de un vector. Para ilustrar, aplicaré [
a vectores atómicos 1D, y luego mostraré cómo esto se generaliza a objetos más complejos y más dimensiones.
4.2.1 Vectores atómicos
Exploremos los diferentes tipos de subconjuntos con un vector simple, x
.
Tenga en cuenta que el número después del punto decimal representa la posición original en el vector.
Hay seis cosas que puede usar para crear subconjuntos de un vector:
Los enteros positivos devuelven elementos en las posiciones especificadas:
c(3, 1)] x[#> [1] 3.3 2.1 order(x)] x[#> [1] 2.1 3.3 4.2 5.4 # Los índices duplicados duplicarán los valores c(1, 1)] x[#> [1] 2.1 2.1 # Los números reales se truncan silenciosamente a enteros c(2.1, 2.9)] x[#> [1] 4.2 4.2
Los enteros negativos excluyen elementos en las posiciones especificadas:
-c(3, 1)] x[#> [1] 4.2 5.4
Tenga en cuenta que no puede mezclar números enteros positivos y negativos en un solo subconjunto:
c(-1, 2)] x[#> Error in x[c(-1, 2)]: only 0's may be mixed with negative subscripts
Los vectores lógicos seleccionan elementos donde el valor lógico correspondiente es
TRUE
. Este es probablemente el tipo de subconjunto más útil porque puede escribir una expresión que usa un vector lógico:c(TRUE, TRUE, FALSE, FALSE)] x[#> [1] 2.1 4.2 > 3] x[x #> [1] 4.2 3.3 5.4
En
x[y]
, ¿qué sucede six
ey
tienen longitudes diferentes? El comportamiento está controlado por las reglas de reciclaje, donde el más corto de los dos se recicla al largo del más largo. Esto es conveniente y fácil de entender cuando uno dex
ey
tiene la longitud uno, pero recomiendo evitar el reciclaje para otras longitudes porque las reglas se aplican de manera inconsistente en la base R.c(TRUE, FALSE)] x[#> [1] 2.1 3.3 # Equivalent to c(TRUE, FALSE, TRUE, FALSE)] x[#> [1] 2.1 3.3
Tenga en cuenta que un valor faltante en el índice siempre produce un valor faltante en la salida:
c(TRUE, TRUE, NA, FALSE)] x[#> [1] 2.1 4.2 NA
Nada devuelve el vector original. Esto no es útil para vectores 1D, pero, como verá en breve, es muy útil para matrices, data frames y arreglos. También puede ser útil junto con la asignación.
x[]#> [1] 2.1 4.2 3.3 5.4
Zero devuelve un vector de longitud cero. Esto no es algo que normalmente haga a propósito, pero puede ser útil para generar datos de prueba.
0] x[#> numeric(0)
Si el vector tiene nombre, también puede usar vectores de caracteres para devolver elementos con nombres coincidentes.
<- setNames(x, letters[1:4])) (y #> a b c d #> 2.1 4.2 3.3 5.4 c("d", "c", "a")] y[#> d c a #> 5.4 3.3 2.1 # Al igual que los índices enteros, puede repetir índices c("a", "a", "a")] y[#> a a a #> 2.1 2.1 2.1 # Al crear un subconjunto con [, los nombres siempre coinciden exactamente <- c(abc = 1, def = 2) z c("a", "d")] z[#> <NA> <NA> #> NA NA
NB: Los factores no se tratan de manera especial cuando se subdividen. Esto significa que la creación de subconjuntos utilizará el vector entero subyacente, no los niveles de caracteres. Esto suele ser inesperado, por lo que debe evitar subconjuntos con factores:
factor("b")]
y[#> a
#> 2.1
4.2.2 Listas
La creación de subconjuntos de una lista funciona de la misma manera que la creación de subconjuntos de un vector atómico. Usar [
siempre devuelve una lista; [[
y $
, como se describe en la Sección 4.3, le permiten extraer elementos de una lista.
4.2.3 Matrices y arreglos
Puede crear subconjuntos de estructuras de dimensiones superiores de tres maneras:
- Con múltiples vectores.
- Con un solo vector.
- Con una matriz.
La forma más común de crear subconjuntos de matrices (2D) y arreglos (>2D) es una generalización simple de subconjuntos 1D: proporcione un índice 1D para cada dimensión, separados por una coma. El subconjunto en blanco ahora es útil porque le permite mantener todas las filas o todas las columnas.
<- matrix(1:9, nrow = 3)
a colnames(a) <- c("A", "B", "C")
1:2, ]
a[#> A B C
#> [1,] 1 4 7
#> [2,] 2 5 8
c(TRUE, FALSE, TRUE), c("B", "A")]
a[#> B A
#> [1,] 4 1
#> [2,] 6 3
0, -2]
a[#> A C
Por defecto, [
simplifica los resultados a la dimensionalidad más baja posible. Por ejemplo, las dos expresiones siguientes devuelven vectores 1D. Aprenderá cómo evitar “reducir” el número de dimensiones en la Sección 4.2.5:
1, ]
a[#> A B C
#> 1 4 7
1, 1]
a[#> A
#> 1
Debido a que tanto las matrices como los arreglos son solo vectores con atributos especiales, puede crear subconjuntos con un solo vector, como si fueran un vector 1D. Tenga en cuenta que las matrices en R se almacenan en orden de columna principal:
<- outer(1:5, 1:5, FUN = "paste", sep = ",")
vals
vals#> [,1] [,2] [,3] [,4] [,5]
#> [1,] "1,1" "1,2" "1,3" "1,4" "1,5"
#> [2,] "2,1" "2,2" "2,3" "2,4" "2,5"
#> [3,] "3,1" "3,2" "3,3" "3,4" "3,5"
#> [4,] "4,1" "4,2" "4,3" "4,4" "4,5"
#> [5,] "5,1" "5,2" "5,3" "5,4" "5,5"
c(4, 15)]
vals[#> [1] "4,1" "5,3"
También puede crear subconjuntos de estructuras de datos de mayor dimensión con una matriz de enteros (o, si se nombra, una matriz de caracteres). Cada fila de la matriz especifica la ubicación de un valor y cada columna corresponde a una dimensión de la matriz. Esto significa que puede usar una matriz de 2 columnas para crear un subconjunto de una matriz, una matriz de 3 columnas para crear un subconjunto de una matriz 3D, etc. El resultado es un vector de valores:
<- matrix(ncol = 2, byrow = TRUE, c(
select 1, 1,
3, 1,
2, 4
))
vals[select]#> [1] "1,1" "3,1" "2,4"
4.2.4 Data frames y tibbles
Data frames tienen las características tanto de listas como de matrices:
Cuando se subdividen con un solo índice, se comportan como listas e indexan las columnas, por lo que
df[1:2]
selecciona las dos primeras columnas.Al crear subconjuntos con dos índices, se comportan como matrices, por lo que
df[1:3, ]
selecciona las primeras tres filas (y todas las columnas)1.
<- data.frame(x = 1:3, y = 3:1, z = letters[1:3])
df
$x == 2, ]
df[df#> x y z
#> 2 2 2 b
c(1, 3), ]
df[#> x y z
#> 1 1 3 a
#> 3 3 1 c
# Hay dos formas de seleccionar columnas de un data frame
# Como una lista
c("x", "z")]
df[#> x z
#> 1 1 a
#> 2 2 b
#> 3 3 c
# Como una matriz
c("x", "z")]
df[, #> x z
#> 1 1 a
#> 2 2 b
#> 3 3 c
# Hay una diferencia importante si selecciona una sola
# columna: el subconjunto de la matriz se simplifica de forma predeterminada, el
# subconjunto de la lista no
str(df["x"])
#> 'data.frame': 3 obs. of 1 variable:
#> $ x: int 1 2 3
str(df[, "x"])
#> int [1:3] 1 2 3
Subdividir un tibble con [
siempre devuelve un tibble:
<- tibble::tibble(x = 1:3, y = 3:1, z = letters[1:3])
df
str(df["x"])
#> tibble [3 × 1] (S3: tbl_df/tbl/data.frame)
#> $ x: int [1:3] 1 2 3
str(df[, "x"])
#> tibble [3 × 1] (S3: tbl_df/tbl/data.frame)
#> $ x: int [1:3] 1 2 3
4.2.5 Preservando la dimensionalidad
De forma predeterminada, subdividir una matriz o data frame con un solo número, un solo nombre o un vector lógico que contenga un solo TRUE
simplificará la salida devuelta, es decir, devolverá un objeto con menor dimensionalidad. Para conservar la dimensionalidad original, debe usar drop = FALSE
.
Para matrices y arreglos, se eliminarán todas las dimensiones con longitud 1:
<- matrix(1:4, nrow = 2) a str(a[1, ]) #> int [1:2] 1 3 str(a[1, , drop = FALSE]) #> int [1, 1:2] 1 3
Data frames con una sola columna devolverán solo el contenido de esa columna:
<- data.frame(a = 1:2, b = 1:2) df str(df[, "a"]) #> int [1:2] 1 2 str(df[, "a", drop = FALSE]) #> 'data.frame': 2 obs. of 1 variable: #> $ a: int 1 2
El comportamiento predeterminado drop = TRUE
es una fuente común de errores en las funciones: verifica su código con un data frame o matriz con varias columnas, y funciona. Seis meses después, usted (u otra persona) lo usa con un data frame de una sola columna y falla con un error desconcertante. Al escribir funciones, acostúmbrese a usar siempre drop = FALSE
al subdividir un objeto 2D. Por esta razón, los tibbles tienen por defecto drop = FALSE
, y [
siempre devuelve otro tibble.
El subconjunto de factores también tiene un argumento drop
, pero su significado es bastante diferente. Controla si se conservan o no los niveles (en lugar de las dimensiones), y su valor predeterminado es FALSE
. Si encuentra que está usando drop = TRUE
mucho, a menudo es una señal de que debería estar usando un vector de caracteres en lugar de un factor.
<- factor(c("a", "b"))
z 1]
z[#> [1] a
#> Levels: a b
1, drop = TRUE]
z[#> [1] a
#> Levels: a
4.2.6 Ejercicios
Solucione cada uno de los siguientes errores comunes de creación de subconjuntos de data frames:
$cyl = 4, ] mtcars[mtcars-1:4, ] mtcars[$cyl <= 5] mtcars[mtcars$cyl == 4 | 6, ] mtcars[mtcars
¿Por qué el siguiente código arroja cinco valores faltantes? (Pista: ¿por qué es diferente de
x[NA_real_]
?)<- 1:5 x NA] x[#> [1] NA NA NA NA NA
¿Qué devuelve
upper.tri()
? ¿Cómo funciona el subconjunto de una matriz con él? ¿Necesitamos reglas adicionales de creación de subconjuntos para describir su comportamiento?<- outer(1:5, 1:5, FUN = "*") x upper.tri(x)] x[
¿Por qué
mtcars[1:20]
devuelve un error? ¿En qué se diferencia de losmtcars[1:20, ]
similares?Implemente su propia función que extraiga las entradas diagonales de una matriz (debería comportarse como
diag(x)
dondex
es una matriz).¿Qué hace
df[is.na(df)] <- 0
? ¿Como funciona?
4.3 Selección de un solo elemento
Hay otros dos operadores de subconjuntos: [[
y $
. [[
se usa para extraer elementos individuales, mientras que x$y
es una abreviatura útil para x[["y"]]
.
4.3.1 [[
[[
es más importante cuando se trabaja con listas porque subdividir una lista con [
siempre devuelve una lista más pequeña. Para ayudar a que esto sea más fácil de entender, podemos usar una metáfora:
Si la lista
x
es un tren que transporta objetos, entoncesx[[5]]
es el objeto en el vagón 5;x[4:6]
es un tren de vagones 4-6.— @RLangTip, https://twitter.com/RLangTip/status/268375867468681216
Usemos esta metáfora para hacer una lista simple:
<- list(1:3, "a", 4:6) x
Al extraer un solo elemento, tiene dos opciones: puede crear un tren más pequeño, es decir, menos vagones, o puede extraer el contenido de un vagón en particular. Esta es la diferencia entre [
y [[
:
Al extraer elementos múltiples (¡o incluso cero!), debe hacer un tren más pequeño:
Debido a que [[
solo puede devolver un solo elemento, debe usarlo con un solo entero positivo o una sola cadena. Si usa un vector con [[
, se subdividirá recursivamente, es decir, x[[c(1, 2)]]
es equivalente a x[[1]][[2]]
. Esta es una característica peculiar que pocos conocen, así que recomiendo evitarla en favor de purrr::pluck()
, sobre la cual aprenderá en la Sección 4.3.3.
Si bien debe usar [[
cuando trabaja con listas, también recomendaría usarlo con vectores atómicos siempre que desee extraer un solo valor. Por ejemplo, en lugar de escribir:
for (i in 2:length(x)) {
<- fun(x[i], out[i - 1])
out[i] }
Es mejor escribir:
for (i in 2:length(x)) {
<- fun(x[[i]], out[[i - 1]])
out[[i]] }
Si lo hace, refuerza la expectativa de que está recibiendo y estableciendo valores individuales.
4.3.2 $
$
es un operador abreviado: x$y
es aproximadamente equivalente a x[["y", exact = FALSE]]
. A menudo se usa para acceder a variables en un data frame, como en mtcars$cyl
o diamonds$carat
. Un error común con $
es usarlo cuando tienes el nombre de una columna almacenada en una variable:
<- "cyl"
var # No funciona - mtcars$var traducido a mtcars[["var"]]
$var
mtcars#> NULL
# En su lugar use [[
mtcars[[var]]#> [1] 6 6 4 6 8 6 8 4 4 6 6 8 8 8 8 8 8 4 4 4 4 8 8 8 8 4 4 4 8 6 8 4
La única diferencia importante entre $
y [[
es que $
automáticamente hace (de izquierda a derecha) coincidencias parciales sin previo aviso:
<- list(abc = 1)
x $a
x#> [1] 1
"a"]]
x[[#> NULL
Para ayudar a evitar este comportamiento, recomiendo configurar la opción global warnPartialMatchDollar
en TRUE
:
options(warnPartialMatchDollar = TRUE)
$a
x#> Warning in x$a: partial match of 'a' to 'abc'
#> [1] 1
(Para data frames, también puede evitar este problema usando tibbles, que nunca hacen coincidencias parciales.)
4.3.3 Índices faltantes y fuera de los límites
Es útil comprender lo que sucede con [[
cuando usa un índice “no válido”. La siguiente tabla resume lo que sucede cuando crea un subconjunto de un vector lógico, una lista y un NULL
con un objeto de longitud cero (como NULL
o logical()
), valores fuera de los límites (OOB) o un valor faltante (por ejemplo, NA_integer_
) con [[
. Cada celda muestra el resultado de dividir la estructura de datos nombrada en la fila por el tipo de índice descrito en la columna. Solo he mostrado los resultados para vectores lógicos, pero otros vectores atómicos se comportan de manera similar, devolviendo elementos del mismo tipo (NB: int = entero; chr = carácter).
row[[col]] |
Longitud cero | OOB (int) | OOB (chr) | Faltante |
---|---|---|---|---|
Atómico | Error | Error | Error | Error |
Lista | Error | Error | NULL |
NULL |
NULL |
NULL |
NULL |
NULL |
NULL |
Si se nombra el vector que se indexa, los nombres de los componentes OOB, faltantes o NULL
serán <NA>
.
Las inconsistencias en la tabla anterior llevaron al desarrollo de purrr::pluck()
y purrr::chuck()
. Cuando falta el elemento, pluck()
siempre devuelve NULL
(o el valor del argumento .default
) y chuck()
siempre arroja un error. El comportamiento de pluck()
lo hace ideal para la indexación en estructuras de datos profundamente anidadas donde el componente que desea puede no existir (como es común cuando se trabaja con datos JSON de API web). pluck()
también te permite mezclar índices enteros y de caracteres, y proporciona un valor predeterminado alternativo si un elemento no existe:
<- list(
x a = list(1, 2, 3),
b = list(3, 4, 5)
)
::pluck(x, "a", 1)
purrr#> [1] 1
::pluck(x, "c", 1)
purrr#> NULL
::pluck(x, "c", 1, .default = NA)
purrr#> [1] NA
4.3.4 @
y slot()
Hay dos operadores de subconjuntos adicionales, que son necesarios para los objetos de S4: @
(equivalente a $
) y slot()
(equivalente a [[
). @
es más restrictivo que $
ya que devolverá un error si la ranura no existe. Estos se describen con más detalle en el Capítulo 15.
4.3.5 Ejercicios
Piense en tantas formas como sea posible para extraer el tercer valor de la variable
cyl
en el conjunto de datosmtcars
.Dado un modelo lineal, por ejemplo,
mod <- lm(mpg ~ wt, data = mtcars)
, extraiga los grados de libertad residuales. Luego extraiga la R al cuadrado del resumen del modelo (summary (mod)
)
4.4 Subconjunto y asignación
Todos los operadores de subconjunto se pueden combinar con la asignación para modificar los valores seleccionados de un vector de entrada: esto se denomina subasignación. La forma básica es x[i] <- valor
:
<- 1:5
x c(1, 2)] <- c(101, 102)
x[
x#> [1] 101 102 3 4 5
Te recomiendo que te asegures de que length(valor)
sea lo mismo que length(x[i])
, y que i
sea único. Esto se debe a que, si bien R reciclará si es necesario, esas reglas son complejas (particularmente si i
contiene valores faltantes o duplicados) y pueden causar problemas.
Con las listas, puede usar x[[i]] <- NULL
para eliminar un componente. Para agregar un literal NULL
, use x[i] <- list(NULL)
:
<- list(a = 1, b = 2)
x "b"]] <- NULL
x[[str(x)
#> List of 1
#> $ a: num 1
<- list(a = 1, b = 2)
y "b"] <- list(NULL)
y[str(y)
#> List of 2
#> $ a: num 1
#> $ b: NULL
La creación de subconjuntos sin nada puede ser útil con la asignación porque conserva la estructura del objeto original. Compara las siguientes dos expresiones. En el primero, mtcars
sigue siendo un data frame porque solo está cambiando el contenido de mtcars
, no mtcars
en sí. En el segundo, mtcars
se convierte en una lista porque está cambiando el objeto al que está vinculado.
<- lapply(mtcars, as.integer)
mtcars[] is.data.frame(mtcars)
#> [1] TRUE
<- lapply(mtcars, as.integer)
mtcars is.data.frame(mtcars)
#> [1] FALSE
4.5 Aplicaciones
Los principios descritos anteriormente tienen una amplia variedad de aplicaciones útiles. A continuación se describen algunos de los más importantes. Si bien muchos de los principios básicos de creación de subconjuntos ya se han incorporado en funciones como subset()
, merge()
y dplyr::arrange()
, será valioso comprender mejor cómo se han implementado esos principios. cuando se encuentra con situaciones en las que las funciones que necesita no existen.
4.5.1 Tablas de búsqueda (subconjunto de caracteres)
La coincidencia de caracteres es una forma poderosa de crear tablas de búsqueda. Digamos que quieres convertir abreviaturas:
<- c("m", "f", "u", "f", "f", "m", "m")
x <- c(m = "Male", f = "Female", u = NA)
lookup
lookup[x]#> m f u f f m m
#> "Male" "Female" NA "Female" "Female" "Male" "Male"
Tenga en cuenta que si no quiere nombres en el resultado, use unname()
para eliminarlos.
unname(lookup[x])
#> [1] "Male" "Female" NA "Female" "Female" "Male" "Male"
4.5.2 Coincidencia y fusión a mano (subconjunto de enteros)
También puede tener tablas de búsqueda más complicadas con múltiples columnas de información. Por ejemplo, supongamos que tenemos un vector de grados enteros y una tabla que describe sus propiedades:
<- c(1, 2, 2, 3, 1)
grades
<- data.frame(
info grade = 3:1,
desc = c("Excellent", "Good", "Poor"),
fail = c(F, F, T)
)
Entonces, digamos que queremos duplicar la tabla info
para que tengamos una fila para cada valor en grade
. Una forma elegante de hacer esto es combinando match()
y un subconjunto de enteros (match(aguja, pajar)
devuelve la posición donde se encuentra cada aguja
en el pajar
).
<- match(grades, info$grade)
id
id#> [1] 3 2 2 1 3
info[id, ]#> grade desc fail
#> 3 1 Poor TRUE
#> 2 2 Good FALSE
#> 2.1 2 Good FALSE
#> 1 3 Excellent FALSE
#> 3.1 1 Poor TRUE
Si está haciendo coincidir varias columnas, primero deberá colapsarlas en una sola columna (con, por ejemplo, interaction ()
). Sin embargo, normalmente es mejor cambiar a una función diseñada específicamente para unir varias tablas como merge()
o dplyr::left_join()
.
4.5.3 Muestras aleatorias y bootstraps (subconjunto de enteros)
Puede usar índices enteros para muestrear o arrancar aleatoriamente un vector o un data frame. Simplemente use sample(n)
para generar una permutación aleatoria de 1:n
, y luego use los resultados para dividir los valores:
<- data.frame(x = c(1, 2, 3, 1, 2), y = 5:1, z = letters[1:5])
df
# Reordenar aleatoriamente
sample(nrow(df)), ]
df[#> x y z
#> 5 2 1 e
#> 3 3 3 c
#> 4 1 2 d
#> 1 1 5 a
#> 2 2 4 b
# Seleccionar aleatoriamente 3 filas
sample(nrow(df), 3), ]
df[#> x y z
#> 4 1 2 d
#> 2 2 4 b
#> 1 1 5 a
# Seleccione 6 réplicas de arranque
sample(nrow(df), 6, replace = TRUE), ]
df[#> x y z
#> 5 2 1 e
#> 5.1 2 1 e
#> 5.2 2 1 e
#> 2 2 4 b
#> 3 3 3 c
#> 3.1 3 3 c
Los argumentos de sample()
controlan el número de muestras a extraer, y también si el muestreo se realiza con o sin reemplazo.
4.5.4 Ordenación (subconjunto de enteros)
order()
toma un vector como entrada y devuelve un vector entero que describe cómo ordenar el vector dividido en subconjuntos2:
<- c("b", "c", "a")
x order(x)
#> [1] 3 1 2
order(x)]
x[#> [1] "a" "b" "c"
Para desempatar, puede proporcionar variables adicionales a order()
. También puede cambiar el orden de ascendente a descendente utilizando decreasing = TRUE
. De forma predeterminada, cualquier valor que falte se colocará al final del vector; sin embargo, puede eliminarlos con na.last = NA
o ponerlos al frente con na.last = FALSE
.
Para dos o más dimensiones, order()
y el subconjunto de enteros facilita el orden de las filas o las columnas de un objeto:
# Reordenar al azar df
<- df[sample(nrow(df)), 3:1]
df2
df2#> z y x
#> 5 e 1 2
#> 1 a 5 1
#> 4 d 2 1
#> 2 b 4 2
#> 3 c 3 3
order(df2$x), ]
df2[#> z y x
#> 1 a 5 1
#> 4 d 2 1
#> 5 e 1 2
#> 2 b 4 2
#> 3 c 3 3
order(names(df2))]
df2[, #> x y z
#> 5 2 1 e
#> 1 1 5 a
#> 4 1 2 d
#> 2 2 4 b
#> 3 3 3 c
Puede ordenar los vectores directamente con sort()
, o de manera similar dplyr::arrange()
, para ordenar un data frame.
4.5.5 Expansión de recuentos agregados (subconjunto de enteros)
A veces obtiene un data frame donde filas idénticas se han colapsado en una y se ha agregado una columna de conteo. rep()
y el subconjunto de enteros hacen que sea fácil de descomprimir, porque podemos aprovechar la vectorización de rep()
: rep(x, y)
repite x[i]
y[i]
veces .
<- data.frame(x = c(2, 4, 1), y = c(9, 11, 6), n = c(3, 5, 1))
df rep(1:nrow(df), df$n)
#> [1] 1 1 1 2 2 2 2 2 3
rep(1:nrow(df), df$n), ]
df[#> x y n
#> 1 2 9 3
#> 1.1 2 9 3
#> 1.2 2 9 3
#> 2 4 11 5
#> 2.1 4 11 5
#> 2.2 4 11 5
#> 2.3 4 11 5
#> 2.4 4 11 5
#> 3 1 6 1
4.5.6 Eliminar columnas de data frames (caracteres )
Hay dos formas de eliminar columnas de un data frame. Puede establecer columnas individuales para NULL
:
<- data.frame(x = 1:3, y = 3:1, z = letters[1:3])
df $z <- NULL df
O puede extraer un subconjunto para devolver solo las columnas que desea:
<- data.frame(x = 1:3, y = 3:1, z = letters[1:3])
df c("x", "y")]
df[#> x y
#> 1 1 3
#> 2 2 2
#> 3 3 1
Si solo conoce las columnas que no desea, use las operaciones de configuración para determinar qué columnas conservar:
setdiff(names(df), "z")]
df[#> x y
#> 1 1 3
#> 2 2 2
#> 3 3 1
4.5.7 Selección de filas en función de una condición (subconjunto lógico)
Debido a que el subconjunto lógico le permite combinar fácilmente condiciones de varias columnas, es probablemente la técnica más utilizada para extraer filas de un data frame.
$gear == 5, ]
mtcars[mtcars#> mpg cyl disp hp drat wt qsec vs am gear carb
#> Porsche 914-2 26.0 4 120.3 91 4.43 2.14 16.7 0 1 5 2
#> Lotus Europa 30.4 4 95.1 113 3.77 1.51 16.9 1 1 5 2
#> Ford Pantera L 15.8 8 351.0 264 4.22 3.17 14.5 0 1 5 4
#> Ferrari Dino 19.7 6 145.0 175 3.62 2.77 15.5 0 1 5 6
#> Maserati Bora 15.0 8 301.0 335 3.54 3.57 14.6 0 1 5 8
$gear == 5 & mtcars$cyl == 4, ]
mtcars[mtcars#> mpg cyl disp hp drat wt qsec vs am gear carb
#> Porsche 914-2 26.0 4 120.3 91 4.43 2.14 16.7 0 1 5 2
#> Lotus Europa 30.4 4 95.1 113 3.77 1.51 16.9 1 1 5 2
Recuerde utilizar los operadores booleanos vectoriales &
y |
, no los operadores escalares de cortocircuito &&
y ||
, que son más útiles dentro de las sentencias if. Y no olvide las leyes de De Morgan, que pueden ser útiles para simplificar las negaciones:
!(X & Y)
es lo mismo que!X | !Y
!(X | Y)
es lo mismo que!X & !Y
Por ejemplo, !(X & !(Y | Z))
se simplifica en !X | !!(Y|Z)
, y luego a !X | Y | Z
.
4.5.8 Álgebra booleana versus conjuntos (lógicos y enteros )
Es útil ser consciente de la equivalencia natural entre las operaciones con conjuntos (subconjuntos enteros) y el álgebra booleana (subconjuntos lógicos). El uso de operaciones de conjuntos es más efectivo cuando:
Quieres encontrar el primero (o el último)
TRUE
.Tienes muy pocos
TRUE
y muchosFALSE
; una representación establecida puede ser más rápida y requerir menos almacenamiento.
which()
le permite convertir una representación booleana en una representación entera. No hay una operación inversa en la base R, pero podemos crear una fácilmente:
<- sample(10) < 4
x which(x)
#> [1] 2 3 4
<- function(x, n) {
unwhich <- rep_len(FALSE, n)
out <- TRUE
out[x]
out
}unwhich(which(x), 10)
#> [1] FALSE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE
Creemos dos vectores lógicos y sus equivalentes enteros, y luego exploremos la relación entre las operaciones booleanas y de conjuntos.
<- 1:10 %% 2 == 0)
(x1 #> [1] FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE
<- which(x1))
(x2 #> [1] 2 4 6 8 10
<- 1:10 %% 5 == 0)
(y1 #> [1] FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE TRUE
<- which(y1))
(y2 #> [1] 5 10
# X & Y <-> intersect(x, y)
& y1
x1 #> [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE
intersect(x2, y2)
#> [1] 10
# X | Y <-> union(x, y)
| y1
x1 #> [1] FALSE TRUE FALSE TRUE TRUE TRUE FALSE TRUE FALSE TRUE
union(x2, y2)
#> [1] 2 4 6 8 10 5
# X & !Y <-> setdiff(x, y)
& !y1
x1 #> [1] FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE FALSE FALSE
setdiff(x2, y2)
#> [1] 2 4 6 8
# xor(X, Y) <-> setdiff(union(x, y), intersect(x, y))
xor(x1, y1)
#> [1] FALSE TRUE FALSE TRUE TRUE TRUE FALSE TRUE FALSE FALSE
setdiff(union(x2, y2), intersect(x2, y2))
#> [1] 2 4 6 8 5
Al aprender subconjuntos por primera vez, un error común es usar x[which(y)]
en lugar de x[y]
. Aquí, which()
no logra nada: cambia de subconjunto lógico a entero, pero el resultado es exactamente el mismo. En casos más generales, hay dos diferencias importantes.
Cuando el vector lógico contiene
NA
, el subconjunto lógico reemplaza estos valores conNA
mientras quewhich()
simplemente elimina estos valores. No es raro usarwhich()
para este efecto secundario, pero no lo recomiendo: nada sobre el nombre “cuál” implica la eliminación de valores faltantes.x[-which(y)]
no es equivalente ax[!y]
: siy
es todo FALSO,which(y)
seráinteger(0)
y-integer(0)
sigue siendointeger(0)
, por lo que no obtendrá valores, en lugar de todos los valores.
En general, evite cambiar de subconjunto lógico a entero a menos que desee, por ejemplo, el primer o último valor TRUE
.
4.5.9 Ejercicios
¿Cómo permutarías aleatoriamente las columnas de un data frame? (Esta es una técnica importante en los bosques aleatorios). ¿Puede permutar simultáneamente las filas y las columnas en un solo paso?
¿Cómo seleccionaría una muestra aleatoria de
m
filas de un data frame? ¿Qué pasaría si la muestra tuviera que ser contigua (es decir, con una fila inicial, una fila final y todas las filas intermedias)?¿Cómo podría poner las columnas en un data frame en orden alfabético?
4.6 Respuestas de la
Los enteros positivos seleccionan elementos en posiciones específicas, los enteros negativos descartan elementos; los vectores lógicos mantienen los elementos en las posiciones correspondientes a
TRUE
; los vectores de caracteres seleccionan elementos con nombres coincidentes.[
selecciona sub-listas: siempre devuelve una lista. Si lo usa con un solo entero positivo, devuelve una lista de longitud uno.[[
selecciona un elemento dentro de una lista.$
es una abreviatura conveniente:x$y
es equivalente ax[["y"]]
.Use
drop = FALSE
si está creando subconjuntos de una matriz, un arreglo o un data frame y desea conservar las dimensiones originales. Casi siempre deberías usarlo cuando hagas subconjuntos dentro de una función.Si
x
es una matriz,x[] <- 0
reemplazará cada elemento con 0, manteniendo el mismo número de filas y columnas. Por el contrario,x <- 0
reemplaza completamente la matriz con el valor 0.Un vector de caracteres con nombre puede actuar como una tabla de búsqueda simple:
c(x = 1, y = 2, z = 3)[c("y", "z", "x")]
Si viene de Python, es probable que esto sea confuso, ya que probablemente esperaría que
df[1:3, 1:2]
seleccione tres columnas y dos filas. Generalmente, R “piensa” en las dimensiones en términos de filas y columnas, mientras que Python lo hace en términos de columnas y filas.↩︎Estos son índices de “extracción”, es decir,
order(x)[i]
es un índice de dónde se encuentra cadax[i]
. No es un índice de dónde debe enviarsex[i]
.↩︎