En otras partes del temario, hemos visto varios tipos de datos con características diferentes.
Este concepto analizará formas de almacenar datos multidimensionales y heterogéneos. En la práctica, la mayoría de los datos del mundo real son así, por lo que ahora llegamos al centro de cómo se usa R (en su mayoría) en la práctica.
A lo largo de las décadas, R ha agregado múltiples tipos de datos para manejar datos tabulares.
Este temario se centrará principalmente en los tibbles, pero es útil conocer las alternativas.
data.frame
En R base, un data.frame es una list de vectors de igual longitud.
Se puede pensar en él como una tabla rectangular de datos, en la que cada columna es homogénea, pero cada fila puede (y por lo general lo hace) contener diferentes tipos de datos.
Un ejemplo para ilustrar esto:
# create the column vectors
languages <- c("Fortran", "R", "Python", "Julia")
created <- c(1957, 1993, 1991, 2012)
has.syllabus <- c(FALSE, TRUE, TRUE, TRUE)
# join columns to create the dataframe
df <- data.frame(languages, created, has.syllabus)
df
#> languages created has.syllabus
#> 1 Fortran 1957 FALSE
#> 2 R 1993 TRUE
#> 3 Python 1991 TRUE
#> 4 Julia 2012 TRUE
Tenemos una columna de strings de caracteres, una columna de números y una columna de booleanos. Cuando se escala, esta es una forma intuitiva de representar muchas colecciones de datos del mundo real.
tibble
El diseño de data.frame es antiguo.
La experiencia de varias décadas, más los patrones cambiantes de cómo se usa R, llevaron a un rediseño, creando una alternativa modernizada en el Tidyverse: los tibbles.
En comparación con R base, los tibbles tienen:
En resumen, un tibble pretende «hacer menos y quejarse más», también descrito como «perezoso y malhumorado».
Los tipos suelen ser intercambiables: cualquier función que acepte un data.frame también aceptará un tibble, y viceversa.
Las conversiones entre los tipos son fáciles, con as_tibble(df) y as.data.frame(tbl).
Para trabajo nuevo, usar tibbles probablemente te ayudará a crear código más robusto.
Sin embargo, el código heredado y los datos heredados son muy abundantes en el mundo de R, por lo que es probable que data.frame siga siendo común durante mucho tiempo.
# column vectors are same as for data.frame
library(tibble)
tbl <- tibble(languages, created, has.syllabus)
tbl
# A tibble: 4 × 3
#> languages created has.syllabus
#> <chr> <dbl> <lgl>
#> 1 Fortran 1957 FALSE
#> 2 R 1993 TRUE
#> 3 Python 1991 TRUE
#> 4 Julia 2012 TRUE
Observa el formato de impresión predeterminado: la línea de comentario con las dimensiones se imprime automáticamente, y los tipos de columna también se muestran.
Los tibbles son una parte central del Tidyverse, así que agrégalos con library(tibble) o library(tidyverse).
De la forma más simple, podemos usar la función tibble() para unir vectores columna, como con data.frame().
Un ejemplo de esto se mostró en una sección anterior.
Si es más conveniente ingresar valores por filas, la función correspondiente es tribble().
En la práctica, hay docenas de formas de crear tibbles, ya que son el formato de salida predeterminado de una diversa gama de funciones del Tidyverse.
El concepto de Programación Funcional discutió la biblioteca purrr para manipular vectores y listas (estructuras de datos unidimensionales).
Para los dataframes (ya sean tradicionales o tibbles), la biblioteca correspondiente a usar es dplyr.
Los dataframes, incluidos los tibbles, se pueden tratar como listas de vectores columna, por lo que la indexación de listas recupera una columna especificada.
tbl
# A tibble: 4 × 3
#> languages created has.syllabus
#> <chr> <dbl> <lgl>
#> 1 Fortran 1957 FALSE
#> 2 R 1993 TRUE
#> 3 Python 1991 TRUE
#> 4 Julia 2012 TRUE
tbl$created
#> [1] 1957 1993 1991 2012
Un dataframe también se puede indexar con indexación estilo matriz.
tbl[c(2, 4), 1:2]
# A tibble: 2 × 2
#> languages created
#> <chr> <dbl>
#> 1 R 1993
#> 2 Julia 2012
En el R moderno con el ecosistema Tidyverse, las funciones de dplyr son generalmente más flexibles y convenientes, y serán el enfoque durante el resto de este concepto.
Obtén una sola columna con pull() con el nombre o el número secuencial (números negativos para contar de derecha a izquierda).
tbl |> pull(created)
#> [1] 1957 1993 1991 2012
Este es el mismo resultado que tbl$created, pero usando una función compatible con pipelines.
Para obtener varias columnas, la función adecuada es select(), que es muy versátil.
Obtén (o elimina) columnas según las propiedades de su nombre o tipo.
# Range with position and/or name
tbl |> select(1:created)
# A tibble: 4 × 2
#> languages created
#> <chr> <dbl>
#> 1 Fortran 1957
#> 2 R 1993
#> 3 Python 1991
#> 4 Julia 2012
# Use type of column
tbl |> select(where(is.numeric))
# A tibble: 4 × 1
#> created
#> <dbl>
#> 1 1957
#> 2 1993
#> 3 1991
#> 4 2012
Se permiten múltiples criterios, usando operadores Boolean &, | y ! (y, o no).
Tal poder parece bastante absurdo con nuestro dataframe de juguete de lenguajes.
Afortunadamente, el tibble starwars está incluido en dplyr, dándonos algo más grande con qué practicar.
# pick a subset of columns
starwars |>
select(name | ends_with("color")) |>
head(5)
# A tibble: 5 × 4
#> name hair_color skin_color eye_color
#> <chr> <chr> <chr> <chr>
#> 1 Luke Skywalker blond fair blue
#> 2 C-3PO NA gold yellow
#> 3 R2-D2 NA white, blue red
#> 4 Darth Vader none white yellow
#> 5 Leia Organa brown light brown
Obtén filas que coincidan con algún criterio con filter(), o exclúyelas con filter_out().
starwars |>
select(name:mass) |>
filter(between(height, 150, 165) & !is.na(mass))
# A tibble: 4 × 3
#> name height mass
#> <chr> <int> <dbl>
#> 1 Leia Organa 150 49
#> 2 Beru Whitesun Lars 165 75
#> 3 Nien Nunb 160 68
#> 4 Ben Quadinaros 163 65
Los criterios de filtro pueden ser arbitrariamente complejos, pero siempre se basan en el contenido de las filas.
Si se conocen los números de las filas, podemos usar una variedad de funciones slice().
starwars |>
select(name | homeworld) |>
slice(20:25)
# A tibble: 6 × 2
#> name homeworld
#> <chr> <chr>
#> 1 Palpatine Naboo
#> 2 Boba Fett Kamino
#> 3 IG-88 NA
#> 4 Bossk Trandosha
#> 5 Lando Calrissian Socorro
#> 6 Lobot Bespin
Primera advertencia: el valor predeterminado de copiar al modificar significa que el tibble original por lo general permanece sin cambios.
La mayoría de las modificaciones se aplican por columnas.
Los nombres de las columnas se pueden cambiar con rename(newname = oldname), o rename_with() para aplicar una función.
Observa la sintaxis dentro de rename().
El contenido de la columna oldname se vincula al nombre newname, de ahí el orden.
El orden de las columnas se puede cambiar con relocate().
Las columnas especificadas se mueven a la posición más a la izquierda por defecto, pero se puede usar un argumento .before o .after para un posicionamiento más preciso.
sw <- starwars |> select(name:species) |> slice(1:4)
sw
# A tibble: 4 × 11
#> name height mass hair_color skin_color eye_color birth_year sex gender homeworld species
#> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr> <chr> <chr>
#> 1 Luke Skywalker 172 77 blond fair blue 19 male masculine Tatooine Human
#> 2 C-3PO 167 75 NA gold yellow 112 none masculine Tatooine Droid
#> 3 R2-D2 96 32 NA white, blue red 33 none masculine Naboo Droid
#> 4 Darth Vader 202 136 none white yellow 41.9 male masculine Tatooine Human
sw |> relocate(c(species, homeworld), .after = name)
# A tibble: 4 × 11
#> name species homeworld height mass hair_color skin_color eye_color birth_year sex gender
#> <chr> <chr> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
#> 1 Luke Skywalker Human Tatooine 172 77 blond fair blue 19 male masculine
#> 2 C-3PO Droid Tatooine 167 75 NA gold yellow 112 none masculine
#> 3 R2-D2 Droid Naboo 96 32 NA white, blue red 33 none masculine
#> 4 Darth Vader Human Tatooine 202 136 none white yellow 41.9 male masculine
Para cambios más grandes, mutate() te permite:
NULL.Claramente, mutate() es potente, potencialmente confuso, y una razón para estar muy agradecido por la copia al modificar.
No hay una razón obvia para preocuparse por el índice de masa corporal de los personajes de Star Wars, pero por si acaso:
starwars |>
select(c(name, species, height, mass)) |>
mutate(BMI = mass / (height / 100)^2) |>
head(4)
# A tibble: 4 × 5
#> name species height mass BMI
#> <chr> <chr> <int> <dbl> <dbl>
#> 1 Luke Skywalker Human 172 77 26.0
#> 2 C-3PO Droid 167 75 26.9
#> 3 R2-D2 Droid 96 32 34.7
#> 4 Darth Vader Human 202 136 33.3
Las operaciones por filas son menos comunes para modificar tibbles individuales (la fusión de múltiples tibbles se discutirá en un concepto posterior).
Una excepción: arrange() ordena las filas por los valores de una o más columnas.
tbl
#> A tibble: 4 × 3
#> languages created has.syllabus
#> <chr> <dbl> <lgl>
#> 1 Fortran 1957 FALSE
#> 2 R 1993 TRUE
#> 3 Python 1991 TRUE
#> 4 Julia 2012 TRUE
tbl |> arrange(languages)
#> A tibble: 4 × 3
#> languages created has.syllabus
#> <chr> <dbl> <lgl>
#> 1 Fortran 1957 FALSE
#> 2 Julia 2012 TRUE
#> 3 Python 1991 TRUE
#> 4 R 1993 TRUE
Un gran huerto de cerezos suele tener un pequeño inventario de árboles sobrantes que le gustaría ofrecer como madera. Esto ayuda a mantener el huerto saludable y, al mismo tiempo, genera ingresos extra. Te han pedido que ayudes a programar algunas funcionalidades para el proceso de selección de árboles cuando llama un posible cliente.
La biblioteca datasets integrada tiene muchos conjuntos de datos de juguete, y este ejercicio usa el conjunto de datos trees.
Para llamar al conjunto de datos, simplemente usa el nombre trees.
library(datasets) # loading is optional
trees |> head(3)
#> A data.frame: 3 × 3
#> Girth Height Volume
#> <dbl> <dbl> <dbl>
#> 1 8.3 70 10.3
#> 2 8.6 65 10.3
#> 3 8.8 63 10.2
Fíjate en las tres columnas: Girth, Height y Volume.
El conjunto de datos está inicialmente en forma de data.frame, pero quizás quieras trabajar con él como un tibble.
Lamentablemente hubo un error administrativo que nunca se corrigió, y una columna del conjunto de datos se llama Girth, pero en realidad es el Diameter.
Define un data.frame/tibble llamado tree_data que corrija el nombre de la columna Girth en trees a Diameter.
head(tree_data, 3)
#> A tibble: 3 × 3
#> Diameter Height Volume
#> <dbl> <dbl> <dbl>
#> 8.3 70 10.3
#> 8.6 65 10.3
#> 8.8 63 10.2
La circunferencia sigue siendo una medida útil, y el peso de la madera también.
La circunferencia se calcula fácilmente con πD, donde D es el diámetro.
El peso de toda la madera se puede calcular con ρV, donde ρ = 35 es la densidad de la madera de cerezo y V es el volumen de la madera.
Define la función girth_n_weight(data, rnd_digits), que toma un dataframe y argumentos numéricos.
La función debe devolver un nuevo dataframe con las dos columnas adicionales: Girth y Weight, redondeadas al número de dígitos que indique rnd_digits.
girth_n_weight(tree_data, 1) |> head(3)
#> A tibble: 3 × 5
#> Diameter Height Volume Girth Weight
#> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 8.3 70 10.3 26.1 360.5
#> 8.6 65 10.3 27.0 360.5
#> 8.8 63 10.2 27.6 357.0
Para cada posible cliente, el huerto guarda una copia especial del conjunto de datos que ayuda con la venta.
Este conjunto de datos tiene las columnas más relevantes, Weight y Height, movidas al frente y está ordenado por Weight.
Define la función orchard_copy(data, important_cols), que toma un dataframe como argumento.
Debe devolver un nuevo dataframe con Weight y Height al frente y las filas ordenadas por Weight.
tree_data |> girth_n_weight() |> orchard_copy() |> head(3)
#> A tibble: 3 × 5
#> Weight Height Diameter Volume Girth
#> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 357 63 9 10 28
#> 360 70 8 10 26
#> 360 65 9 10 27
Cada posible cliente recibirá una versión personal del conjunto de datos según sus preferencias. Para eso, el cliente puede especificar: la altura mínima, la altura máxima y el peso máximo de un solo árbol.
Define la función customer_copy(data, min_height, max_height, max_weight).
Devuelve una copia del dataframe para el cliente con las columnas Height, Weight, Diameter y Girth, con las filas que estén dentro de las restricciones.
tree_data |>
girth_n_weight(1) |>
orchard_copy() |>
customer_copy(65, 75, 1500) |>
head(3)
#> A tibble: 3 × 4
#> Height Weight Diameter Girth
#> <dbl> <dbl> <dbl> <dbl>
#> 70 360.5 8.3 26.1
#> 65 360.5 8.6 27.0
#> 66 546.0 11.0 34.6
Regístrate en Exercism para aprender y dominar R con 21 conceptos111 ejercicios y mentoría humana real, todo gratis.