Capítulo18 Importar datos

Fecha de la última revisión

## [1] "2026-10-08"

18.1 Temas

  • De dónde salen los datos: un archivo tuyo, la web, un paquete
  • Dónde está el archivo: el proyecto y las rutas relativas
  • read_csv() y su familia
  • Los argumentos que de verdad se usan: skip, na, col_names, col_types
  • Revisar lo que llegó: glimpse(), spec(), problems()
  • En qué se diferencia read_csv() de read.csv()
  • Cuando la columna llega con el tipo equivocado: las funciones parse_*()
  • Números, acentos y codificación, factores, fechas
  • Leer varios archivos a la vez
  • Archivos de Excel
  • Datos de la web
  • Escribir un archivo: write_csv()
  • Otros formatos: SPSS, bases de datos, hojas de Google

18.2 De dónde salen los datos

Hasta ahora casi todos los datos venían de un paquete: escribías library(datos) y vuelos aparecía. Eso es cómodo para aprender y no se parece en nada a la vida real. En un proyecto de verdad los datos llegan de tres sitios:

  1. De un archivo tuyo, dentro de la carpeta del proyecto. Es lo más común y lo más reproducible.
  2. De internet, con una dirección que apunta a un archivo .csv. Es rápido y siempre está al día, pero depende de que haya conexión y de que quien publica no cambie el archivo debajo de ti.
  3. De un paquete, como hemos hecho en clase.

La regla práctica que conviene adoptar desde ahora: baja el archivo una vez, guárdalo en Datos/, y de ahí en adelante trabaja con tu copia. Así tu análisis corre igual dentro de un año, sin internet y aunque la fuente original haya desaparecido.

Por eso el capítulo sigue ese mismo orden: primero aprendemos a leer bien un archivo que ya está en el proyecto, después a revisar y corregir lo que llegó, y solo al final vamos a buscar datos a la web.

Importar es el paso donde se cuelan la mitad de los errores de un análisis, y son errores silenciosos: una columna que llega como texto, un decimal leído como separador de miles, un acento convertido en símbolo raro. Después de cada importación, mira la tabla antes de calcular nada.


18.3 Dónde está el archivo: rutas relativas

Antes de leer un archivo, R tiene que encontrarlo. Cuando trabajas dentro de un proyecto de RStudio (el archivo .Rproj), R busca siempre a partir de la carpeta del proyecto. Por eso basta con escribir la ruta desde ahí:

"Datos/Newborn_Karn_Penrose.csv"

Eso se lee: “dentro de la carpeta del proyecto, entra a Datos y abre ese archivo”. Se llama ruta relativa, y ya la usaste en el capítulo de flujo de trabajo cuando leíste los vuelos de San Juan.

Si alguna vez dudas de dónde está buscando R, pregúntaselo:

getwd()              # la carpeta desde donde R está buscando
list.files("Datos")  # los archivos que R ve dentro de Datos/

No uses rutas absolutas, las que empiezan en la raíz de tu computadora, como "C:/Users/maria/Desktop/datos.csv" o "~/Google Drive/Proyecto/datos.csv". Funcionan en tu computadora y en ninguna otra: ni en la de tu compañero, ni en la del profesor, ni en la tuya cuando muevas la carpeta. Con el archivo dentro del proyecto y una ruta relativa, el script corre igual en cualquier máquina.

Dentro de las comillas de la ruta, presiona la tecla Tab: RStudio te muestra las carpetas y los archivos que existen y completa el nombre por ti. Si el archivo no aparece en la lista, no está donde tú crees, y es mejor saberlo antes de correr el código.


18.4 Leer un archivo con read_csv()

Esta es la manera que vas a usar el 90 % de las veces: el archivo está dentro de la carpeta del proyecto y se lee con read_csv() y una ruta relativa.

library(tidyverse)
library(readr)
Newborn_Karn_Penrose <- read_csv("Datos/Newborn_Karn_Penrose.csv")

head(Newborn_Karn_Penrose)
## # A tibble: 6 × 4
##   Mother_age Count_Non_survivors Count_Total_Birth Birth_weight
##        <dbl>               <dbl>             <dbl>        <dbl>
## 1         16                   0                 1          9.5
## 2         16                   0                 2          9  
## 3         16                   0                 1          8.5
## 4         16                   0                 3          8  
## 5         16                   0                 7          7.5
## 6         16                   0                 2          7

Tres cosas pasaron en esa línea: read_csv() abrió el archivo, tomó la primera fila como los nombres de las columnas, y adivinó el tipo de cada columna mirando sus valores. Cuando la corras en tu consola verás además un mensaje que dice cuántas filas y columnas leyó y qué tipo le asignó a cada una. Ese mensaje no es un error: es el informe de lo que readr decidió, y conviene leerlo.

Estos datos vienen de un estudio clásico de peso al nacer: cada fila es una combinación de edad de la madre y peso del bebé, con cuántos nacimientos hubo y cuántos no sobrevivieron. Fíjate en que no es una fila por bebé: son conteos agrupados, y eso cambia cómo hay que leerlos.

Una vez importada, la tabla se comporta como cualquier otra: todo lo que ya sabes de dplyr y ggplot2 funciona. Por ejemplo, hacer una columna de la proporción de los niños que no sobrevivieron:

Newborn_Karn_Penrose %>%
  mutate(Percent_Decease = Count_Non_survivors/Count_Total_Birth)
## # A tibble: 261 × 5
##    Mother_age Count_Non_survivors Count_Total_Birth Birth_weight Percent_Decease
##         <dbl>               <dbl>             <dbl>        <dbl>           <dbl>
##  1         16                   0                 1          9.5               0
##  2         16                   0                 2          9                 0
##  3         16                   0                 1          8.5               0
##  4         16                   0                 3          8                 0
##  5         16                   0                 7          7.5               0
##  6         16                   0                 2          7                 0
##  7         16                   0                 4          6.5               0
##  8         16                   0                 2          6                 0
##  9         16                   0                 2          5.5               0
## 10         16                   0                 1          5                 0
## # ℹ 251 more rows

Y lo mismo, ahora en gráfica:

Newborn_Karn_Penrose %>%
  mutate(Percent_Decease = Count_Non_survivors/Count_Total_Birth) %>%
  ggplot(aes(Birth_weight, Percent_Decease)) +
  geom_point() +
  geom_smooth()

Newborn_Karn_Penrose %>%
  mutate(Percent_Decease = Count_Non_survivors/Count_Total_Birth) %>%
  ggplot(aes(Birth_weight, Percent_Decease, color=Mother_age)) +
  geom_point(aes(color=factor(Mother_age))) +
  geom_smooth(se=FALSE, aes(color=factor(Mother_age)))

Fíjate en el factor(Mother_age) de la última gráfica: la edad llegó como número, y para usarla como categoría de color hubo que convertirla. Más adelante en el capítulo verás cómo pedirle a read_csv() que la lea con el tipo que quieres desde el principio.

18.4.1 La familia de funciones de readr

read_csv() tiene hermanas. Cada función de la lista corresponde a un formato de archivo distinto, y lo único que cambia entre ellas es qué separa una columna de la siguiente:

  • read_csv(): separado por coma
  • read_csv2(): separado por punto y coma (común en archivos de países que usan la coma como decimal)
  • read_tsv(): separado por tabulador
  • read_delim(): separado por el delimitador que tú le digas, con el argumento delim
  • read_table(): columnas separadas por espacios en blanco
  • read_fwf(): columnas de ancho fijo, donde cada columna ocupa siempre las mismas posiciones; los anchos se le indican con fwf_widths() o fwf_positions()

read_csv() (readr): importar un archivo de texto a un tibble.

  • read_csv("Datos/archivo.csv"): lee un CSV; el primer argumento es la ruta del archivo (o una dirección de internet).
  • Variantes: read_csv2() (separado por ;), read_tsv() (tabuladores), read_delim() (delimitador a elección).
  • Todas comparten los mismos argumentos: lo que aprendas para read_csv() sirve para el resto de la familia.

18.4.2 Los argumentos que de verdad se usan

Los archivos del mundo real casi nunca llegan limpios: traen un título antes de la tabla, les falta la fila de nombres, o escriben los valores faltantes a su manera. Para cada uno de esos casos hay un argumento.

Argumentos de read_csv(): lo que hay que saber para archivos del mundo real.

  • skip = 2: salta las primeras líneas. Sirve cuando el archivo trae un título o un logo antes de la tabla.
  • comment = "#": ignora las líneas que empiecen con ese símbolo.
  • col_names = FALSE: el archivo no tiene fila de nombres. También acepta un vector con los nombres que quieras ponerle.
  • na = c("", "NA", "n/a", "."): qué textos hay que tratar como valor faltante. Por defecto solo "" y "NA".
  • col_types = cols(codigo = col_character()): obliga a una columna a leerse con el tipo que tú digas. Es la solución cuando un código postal o un identificador que empieza con cero se lee como número y pierde el cero.

18.4.3 Un archivo sucio de verdad

Para ver de qué estamos hablando, en Datos/ hay un archivo pequeño, plantas_sucio.csv, hecho a propósito con varios de estos problemas. Si lo abres con un editor de texto (o haciendo clic sobre él en el panel Files de RStudio y escogiendo View File), esto es lo que contiene:

Registro de plantas del invernadero
Archivo de ejemplo para el curso BIOL4026
# las alturas están en centímetros
planta,codigo,altura,flores
P1,00791,34.5,3
P2,00725,n/d,0
P3,00612,27,.
P4,01004,41.2,5

Y así se ve el mismo archivo si lo abres en Excel, que es como casi siempre lo vas a mirar primero:

A B C D
1 Registro de plantas del invernadero
2 Archivo de ejemplo para el curso BIOL4026
3 # las alturas están en centímetros
4 planta codigo altura flores
5 P1 791 34.5 3
6 P2 725 n/d 0
7 P3 612 27 .
8 P4 1004 41.2 5

Mirando la hoja se pueden nombrar los problemas uno por uno, y cada uno corresponde a un argumento de la lista:

  • Filas 1 y 2: un título antes de la tabla. Se saltan con skip = 2.
  • Fila 3: una nota que empieza con #. Se ignora con comment = "#".
  • Fila 4: aquí es donde de verdad están los nombres de las columnas.
  • Celdas C6 y D7: dos maneras distintas de escribir “no hay dato”, n/d y un punto. Se declaran con na.
  • Columna B: compara con el archivo de texto. Los códigos eran 00791, 00725…, y Excel los muestra como 791, 725…: decidió que eran números y les quitó los ceros. Es justo el problema que col_types evita en R.

Mirar un .csv en Excel no le hace daño. Guardarlo desde Excel sí: si aceptas guardar los cambios, los códigos quedan escritos sin sus ceros y el archivo original se pierde. Para mirar, abre, mira y cierra sin guardar.

Ahora lo leemos en R sin ningún argumento, para ver qué pasa:

read_csv("Datos/plantas_sucio.csv")
## # A tibble: 7 × 1
##   `Registro de plantas del invernadero`    
##   <chr>                                    
## 1 Archivo de ejemplo para el curso BIOL4026
## 2 # las alturas están en centímetros       
## 3 planta,codigo,altura,flores              
## 4 P1,00791,34.5,3                          
## 5 P2,00725,n/d,0                           
## 6 P3,00612,27,.                            
## 7 P4,01004,41.2,5

read_csv() tomó la primera línea, el título, como si fuera la fila de nombres. Como esa línea no tiene comas, concluyó que el archivo tiene una sola columna, y todo lo demás quedó amontonado ahí. No dio error, y la tabla no sirve para nada. Vamos a ver cada argumento por separado y después volvemos a este archivo para arreglarlo.

18.4.4 Cada argumento por separado

Para ver cada argumento en acción no hace falta un archivo: se puede escribir un CSV pequeño directamente en el código, dentro de I(). El \n marca el salto de línea. Es un truco muy útil para experimentar, y para preparar un ejemplo corto cuando le pides ayuda a alguien.


Ejemplo 1. Un título antes de la tabla: skip

Un archivo con dos líneas de título antes de la tabla, resuelto con skip:

read_csv(I("Datos del laboratorio
Tomados en marzo
x,y,z
1,2,3
4,5,6"), skip = 2)
## # A tibble: 2 × 3
##       x     y     z
##   <dbl> <dbl> <dbl>
## 1     1     2     3
## 2     4     5     6

Ejemplo 2. Líneas de comentario: comment

Un archivo con líneas de comentario, resuelto con comment:

read_csv(I("# Esta línea es una nota, no un dato
x,y,z
1,2,3"), comment = "#")
## # A tibble: 1 × 3
##       x     y     z
##   <dbl> <dbl> <dbl>
## 1     1     2     3

Ejemplo 3. Sin fila de nombres: col_names

Un archivo sin fila de nombres. Primero con col_names = FALSE, y readr inventa los nombres X1, X2, X3; después dándole los nombres que queremos:

read_csv(I("1,2,3\n4,5,6"), col_names = FALSE)
## # A tibble: 2 × 3
##      X1    X2    X3
##   <dbl> <dbl> <dbl>
## 1     1     2     3
## 2     4     5     6
read_csv(I("1,2,3\n4,5,6"), col_names = c("largo", "ancho", "peso"))
## # A tibble: 2 × 3
##   largo ancho  peso
##   <dbl> <dbl> <dbl>
## 1     1     2     3
## 2     4     5     6

Ejemplo 4. Faltantes escritos a su manera: na

Un archivo que escribe los faltantes con un punto, resuelto con na:

read_csv(I("planta,altura\nA,10\nB,.\nC,12"), na = ".")
## # A tibble: 3 × 2
##   planta altura
##   <chr>   <dbl>
## 1 A          10
## 2 B          NA
## 3 C          12

Compara con lo que pasa si no se lo dices: el punto es texto, así que toda la columna altura se lee como texto y ya no se le puede sacar un promedio.

read_csv(I("planta,altura\nA,10\nB,.\nC,12"))
## # A tibble: 3 × 2
##   planta altura
##   <chr>  <chr> 
## 1 A      10    
## 2 B      .     
## 3 C      12

18.4.5 De vuelta al archivo sucio

Con los argumentos ya vistos, arreglamos plantas_sucio.csv paso a paso.

Paso 1. Saltar el título e ignorar la nota: skip y comment

Primero saltamos las dos líneas de título e ignoramos la nota:

read_csv("Datos/plantas_sucio.csv", skip = 2, comment = "#")
## # A tibble: 4 × 4
##   planta codigo altura flores
##   <chr>  <chr>  <chr>  <chr> 
## 1 P1     00791  34.5   3     
## 2 P2     00725  n/d    0     
## 3 P3     00612  27     .     
## 4 P4     01004  41.2   5

Ya hay cuatro columnas con sus nombres. Pero mira los tipos de altura y de flores: son texto (<chr>), por culpa del n/d y del punto.


Paso 2. Declarar los valores faltantes: na

Le decimos a read_csv() cuáles textos significan “no hay dato”:

read_csv("Datos/plantas_sucio.csv", skip = 2, comment = "#",
         na = c("", "NA", "n/d", "."))
## # A tibble: 4 × 4
##   planta codigo altura flores
##   <chr>  <chr>   <dbl>  <dbl>
## 1 P1     00791    34.5      3
## 2 P2     00725    NA        0
## 3 P3     00612    27       NA
## 4 P4     01004    41.2      5

Ahora las dos son números y los faltantes aparecen como NA.


Paso 3. Proteger los códigos: col_types

Falta un detalle: codigo es un identificador, no una cantidad, así que pedimos que se lea como texto para asegurarnos de que conserve sus ceros. Esta es la lectura completa:

plantas <- read_csv("Datos/plantas_sucio.csv", skip = 2, comment = "#",
                    na = c("", "NA", "n/d", "."),
                    col_types = cols(codigo = col_character()))

plantas
## # A tibble: 4 × 4
##   planta codigo altura flores
##   <chr>  <chr>   <dbl>  <dbl>
## 1 P1     00791    34.5      3
## 2 P2     00725    NA        0
## 3 P3     00612    27       NA
## 4 P4     01004    41.2      5

Compara esta tabla con la hoja de Excel de arriba: cuatro filas, cuatro columnas, los códigos con sus ceros y dos NA donde no había dato. Cuatro argumentos, un problema cada uno.

read_csv() adivina el tipo de cada columna mirando las primeras mil filas. Si el problema aparece en la fila 5000 (una celda que dice “no medido” en una columna de números), la adivinanza sale mal. Cuando una columna llegue con el tipo equivocado, no la arregles después con as.numeric(): dile a read_csv() cómo leerla desde el principio con col_types.


18.5 Revisar lo que llegó

Importar no termina cuando la línea corre sin error. Termina cuando compruebas que la tabla que tienes en R es la que había en el archivo. Son tres preguntas, siempre las mismas:

  1. ¿Llegaron todas las filas y todas las columnas?
  2. ¿Cada columna tiene el tipo que debe tener?
  3. ¿Dónde hay valores faltantes, y son los que esperaba?

glimpse() contesta las dos primeras de un vistazo: el número de filas y de columnas, y al lado de cada columna su tipo (<dbl> número, <chr> texto, <date> fecha) y sus primeros valores.

glimpse(Newborn_Karn_Penrose)
## Rows: 261
## Columns: 4
## $ Mother_age          <dbl> 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 18, 18…
## $ Count_Non_survivors <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1,…
## $ Count_Total_Birth   <dbl> 1, 2, 1, 3, 7, 2, 4, 2, 2, 1, 1, 1, 4, 10, 10, 38,…
## $ Birth_weight        <dbl> 9.5, 9.0, 8.5, 8.0, 7.5, 7.0, 6.5, 6.0, 5.5, 5.0, …

spec() enseña la decisión que tomó readr para cada columna, escrita en el mismo formato que usa el argumento col_types. Eso permite copiarla, cambiar la columna que salió mal y volver a leer el archivo.

spec(Newborn_Karn_Penrose)
## cols(
##   Mother_age = col_double(),
##   Count_Non_survivors = col_double(),
##   Count_Total_Birth = col_double(),
##   Birth_weight = col_double()
## )

Para la tercera pregunta, cuenta los faltantes de cada columna:

colSums(is.na(Newborn_Karn_Penrose))
##          Mother_age Count_Non_survivors   Count_Total_Birth        Birth_weight 
##                   0                   0                   0                   0

Revisar una importación: las funciones para mirar antes de calcular.

  • glimpse(tabla): dimensiones, tipo y primeros valores de cada columna.
  • spec(tabla): el tipo que readr le asignó a cada columna, listo para copiar en col_types.
  • problems(tabla): la lista de las celdas que readr no pudo leer como esperaba, con su fila y su columna. Si devuelve una tabla vacía, no hubo problemas.
  • colSums(is.na(tabla)): cuántos valores faltantes hay en cada columna.

El mensaje que imprime read_csv() al leer es útil mientras estás explorando. Cuando ya revisaste el archivo y no quieres verlo más (por ejemplo, en un informe final), añade el argumento show_col_types = FALSE.


18.6 Comparar con base R

read.csv() y read_csv() se parecen tanto que es fácil confundirlas, y no son lo mismo. read.csv() es la de base R, con punto; read_csv() es la de readr, con guion bajo. Prefiere siempre la segunda:

read.csv() (base R) read_csv() (readr)
Qué devuelve un data.frame un tibble
Nombres de columna los “arregla”: Area Policiaca pasa a Area.Policiaca los deja tal cual
Texto en versiones viejas lo convertía en factor siempre texto
Velocidad lenta con archivos grandes mucho más rápida
Informe ninguno dice cómo interpretó cada columna

En resumen: read.csv() funciona, pero toma decisiones por su cuenta y no te avisa. read_csv() hace menos y te cuenta lo que hizo, que es lo que uno quiere cuando los datos son de verdad.


18.7 Segmentar un vector

Cuando read_csv() no acierta con el tipo de una columna, o cuando los datos llegan de otra parte, hay que convertir el texto a mano. De eso se encargan las funciones parse_*(): cada una toma un vector de texto y devuelve un vector del tipo que dice su nombre.

Son también las funciones que read_csv() usa por dentro, así que entenderlas explica por qué a veces adivina mal.

parse_*() (readr): convertir texto al tipo correcto.

  • Qué hacen: parse_integer(), parse_double(), parse_number(), parse_logical(), parse_date(), parse_factor() convierten cadenas de texto a números, fechas, etc.
  • parse_number("$123.45"): extrae el número ignorando símbolos como $ o %.
  • locale = locale(...): controla la codificación, el separador de miles y el formato de fecha.

En el bloque siguiente, fíjate en el contraste: parse_logical() funciona con x, que contiene textos que R reconoce como lógicos, y no puede con y, que contiene nombres de personas: devuelve NA y deja anotado el problema (lo que esperaba y lo que encontró). Y fíjate sobre todo en mean(z): z parece numérico pero está entre comillas, así que es texto, y el promedio da NA. Una vez convertido con parse_integer(), el promedio sí se puede calcular.

x= c("TRUE", "FALSE", "NA")
x
## [1] "TRUE"  "FALSE" "NA"
str(parse_logical(x))
##  logi [1:3] TRUE FALSE NA
y= c("Carlos", "Kelvin")

y
## [1] "Carlos" "Kelvin"
str(parse_logical(y))
##  logi [1:2] NA NA
##  - attr(*, "problems")= tibble [2 × 4] (S3: tbl_df/tbl/data.frame)
##   ..$ row     : int [1:2] 1 2
##   ..$ col     : int [1:2] NA NA
##   ..$ expected: chr [1:2] "1/0/T/F/TRUE/FALSE" "1/0/T/F/TRUE/FALSE"
##   ..$ actual  : chr [1:2] "Carlos" "Kelvin"
z=c("1", "2", "3")
z
## [1] "1" "2" "3"
mean(z)
## [1] NA
z1=parse_integer(z)
str(z1)
##  int [1:3] 1 2 3
mean(z1)
## [1] 2
str(parse_date(c("2010-01-01", "1979-10-14")))
##  Date[1:2], format: "2010-01-01" "1979-10-14"

En este libro las advertencias de R están apagadas para que las páginas queden limpias. En tu consola, parse_logical(y) y mean(z) sí imprimen una advertencia (Warning). No es un error: R hizo lo que pudo, devolvió NA y te está avisando.

Cuando una conversión falla en algunas celdas, problems() te dice exactamente en cuáles: la fila, lo que readr esperaba y lo que encontró.

medidas <- parse_integer(c("123", "345", "abc", "123.45"))
medidas
## [1] 123 345  NA  NA
## attr(,"problems")
## # A tibble: 2 × 4
##     row   col expected               actual
##   <int> <int> <chr>                  <chr> 
## 1     3    NA no trailing characters abc   
## 2     4    NA no trailing characters 123.45
problems(medidas)
## # A tibble: 2 × 4
##     row   col expected               actual
##   <int> <int> <chr>                  <chr> 
## 1     3    NA no trailing characters abc   
## 2     4    NA no trailing characters 123.45

parse_number() es la más útil de todas en la práctica: se queda con el número y tira todo lo demás. Con ella se limpian de un golpe las columnas de precios, de porcentajes y de cantidades escritas con símbolos.

m=c("$4000", "$500" )
parse_number(m)
## [1] 4000  500
parse_number("$100")
## [1] 100
parse_number("20%")
## [1] 20
parse_number("It cost $123.45")
## [1] 123.45

18.7.1 Números

Con los números hay dos trampas, y las dos son cuestión de país.

La primera es el separador decimal: en inglés se escribe 1.5 y en español 1,5. La segunda es el separador de miles: en inglés 1,000,000 y en español 1.000.000. Es decir, el mismo signo significa cosas contrarias según de dónde venga el archivo.

Se resuelven con locale(), que le dice a readr en qué convención está escrito el archivo:

parse_double("1,5", locale = locale(decimal_mark = ","))
## [1] 1.5
parse_number("1.000.000", locale = locale(grouping_mark = "."))
## [1] 1e+06

¿Y si el mismo número trae los dos signos, como 1.234.567,89? Entonces se declaran los dos en el mismo locale(), y se usa parse_number(), que es la que sabe quitar los separadores de miles (parse_double() no los acepta):

espanol <- locale(decimal_mark = ",", grouping_mark = ".")

precio <- parse_number("1.234.567,89", locale = espanol)
precio
## [1] 1234568
print(precio, digits = 10)
## [1] 1234567.89

Dos detalles de ese resultado. Primero, R enseña por defecto solo siete cifras, así que la primera línea parece haber perdido los decimales; no se perdieron, y print(precio, digits = 10) lo demuestra. Segundo, el locale() se guardó en un objeto, espanol, para poder usarlo igual al leer el archivo completo: read_csv2("Datos/archivo.csv", locale = espanol).

Este error es traicionero porque no da ningún mensaje: si un archivo en español se lee con la convención inglesa, 1,5 se convierte en 15 y el análisis sigue con números diez veces más grandes. Cuando importes datos de una agencia de gobierno, mira siempre un par de valores y compáralos con el archivo original.

18.7.2 Cadena de texto

Aquí aparece el problema de los acentos. Una computadora no guarda letras, guarda números, y la tabla que traduce números a letras se llama codificación. Hoy casi todo usa UTF-8, que tiene sitio para la ñ, los acentos y los signos de apertura. Los archivos viejos, sobre todo los que salieron de un Excel en Windows, suelen venir en Latin1.

charToRaw() enseña los números que hay detrás de un texto. Compara el nombre sin acento y con acento: cada letra sin acento ocupa un número, y la ó necesita más de uno.

charToRaw("Ramon")
## [1] 52 61 6d 6f 6e
charToRaw("Ramón")
## [1] 52 61 6d c3 b3 6e

Si abres un archivo con la codificación equivocada, la ñ se convierte en símbolos raros. En el bloque siguiente, x1 y x2 son textos guardados en codificaciones viejas; parse_character() los recupera cuando se le dice cuál es:

x1 <- "El Ni\xf1o was particularly bad this year"
x2 <- "\x82\xb1\x82\xf1\x82\xc9\x82\xbf\x82\xcd"
x3 <- "The boy was particularly bad this year"

parse_character(x1, locale = locale(encoding = "Latin1"))
## [1] "El Niño was particularly bad this year"
parse_character(x2, locale = locale(encoding = "Shift-JIS"))
## [1] "こんにちは"

Y cuando no sabes cuál es la codificación, guess_encoding() intenta adivinarla:

guess_encoding(charToRaw(x1))
## # A tibble: 2 × 2
##   encoding   confidence
##   <chr>           <dbl>
## 1 ISO-8859-1       0.46
## 2 ISO-8859-9       0.23
guess_encoding(charToRaw(x3))
## # A tibble: 1 × 2
##   encoding confidence
##   <chr>         <dbl>
## 1 ASCII             1

Cuando veas Ni�o o Ni√±o en una tabla recién importada, no es que los datos estén dañados: es la codificación. Prueba read_csv(archivo, locale = locale(encoding = "Latin1")), y si no sabes cuál es, pregúntaselo a guess_encoding("Datos/archivo.csv"), que también acepta la ruta de un archivo.

18.7.2.1 Tu nombre en otros alfabetos

Una manera divertida de ver que detrás de cada letra hay números es escribir un nombre en otro alfabeto. La función stri_trans_general() del paquete stringi hace una transliteración: cambia cada sonido por la letra que le corresponde en el otro alfabeto. No es una traducción, el nombre sigue sonando igual.

library(stringi)

nombre <- "Ramón"

griego  <- stri_trans_general(nombre, "Latin-Greek")
ruso    <- stri_trans_general(nombre, "Latin-Cyrillic")
japones <- stri_trans_general(nombre, "Latin-Katakana")

griego
## [1] "Ρ̓αμόν"
ruso
## [1] "Рамо́н"
japones
## [1] "ラモ́ン"

Ahora mira cuánto ocupa cada versión. nchar() cuenta letras, y con type = "bytes" cuenta los números que la computadora guarda de verdad:

nchar(nombre)
## [1] 5
nchar(nombre, type = "bytes")
## [1] 6
nchar(griego)
## [1] 6
nchar(griego, type = "bytes")
## [1] 12
charToRaw(griego)
##  [1] ce a1 cc 93 ce b1 ce bc cf 8c ce bd

En UTF-8 las letras sin acento del alfabeto latino ocupan un byte; las letras con acento, las griegas y las cirílicas ocupan dos, y los caracteres japoneses ocupan tres. Por eso un mismo nombre “pesa” distinto según el alfabeto.

Y aquí se cierra el círculo con el problema de la codificación. Antes de UTF-8 cada idioma tenía su propia tabla, y la del griego se llama ISO-8859-7. Si guardamos el nombre en esa tabla vieja, solo se recupera diciéndole a readr cuál es, igual que con x1 y x2:

griego_viejo <- iconv(griego, from = "UTF-8", to = "ISO-8859-7")

charToRaw(griego_viejo)
## [1] 4e 41
parse_character(griego_viejo, locale = locale(encoding = "ISO-8859-7"))
## [1] NA

Inténtalo con tu nombre. Guarda tu nombre (con sus acentos, si los tiene) en un objeto y transliteralo al griego, al ruso ("Latin-Cyrillic") y al japonés ("Latin-Katakana"). Para cada versión, cuenta las letras con nchar() y los bytes con nchar(..., type = "bytes").

Verificación: en griego y en ruso, cada letra del nuevo alfabeto ocupa dos bytes; en japonés, cada carácter ocupa tres.

En palabras: ¿cuál de las versiones de tu nombre ocupa más bytes, y por qué? Si alguien abriera tu nombre en ruso con la codificación Latin1, ¿vería un error o vería símbolos raros? Explica la diferencia.

# Escribe tu código aquí

18.7.3 Factores

parse_factor() convierte texto en factor, pero pidiéndote la lista de niveles que esperas. Eso es útil precisamente porque avisa cuando aparece un valor que no estaba en tu lista: una errata, un “Carolína” con acento entre mil “Carolina” sin él.

tamano <- parse_factor(c("alta", "baja", "media"), levels = c("alta", "baja"))
tamano
## [1] alta baja <NA>
## attr(,"problems")
## # A tibble: 1 × 4
##     row   col expected           actual
##   <int> <int> <chr>              <chr> 
## 1     3    NA value in level set media 
## Levels: alta baja
problems(tamano)
## # A tibble: 1 × 4
##     row   col expected           actual
##   <int> <int> <chr>              <chr> 
## 1     3    NA value in level set media

El valor "media" no estaba en los niveles, así que quedó como NA, y problems() dice en qué posición estaba.

18.7.4 Fechas, fechas-horas, horas

Una fecha leída como texto no sirve de nada: no se puede ordenar bien, no se le puede restar otra fecha, no se puede agrupar por mes. Convertirla es de los primeros pasos después de importar.

  • parse_date(): una fecha
  • parse_datetime(): una fecha con su hora
  • parse_time(): solo la hora

Si la fecha viene escrita como año-mes-día, readr la entiende sola. Para cualquier otro orden hay que describírselo con format: %d es el día, %m el mes, %Y el año con cuatro cifras, %y el año con dos.

parse_date("2016-08-01")
## [1] "2016-08-01"
parse_date("01/08/2016", format = "%d/%m/%Y")
## [1] "2016-08-01"
parse_date("01/02/15", "%m/%d/%y")
## [1] "2015-01-02"
parse_time("15:30:00")
## 15:30:00

01/08/2016 es el 1 de agosto para media humanidad y el 8 de enero para la otra mitad. Si no dices el format, R usa el suyo y puede acertar por casualidad durante los primeros doce días del mes. Este capítulo solo abre el tema: las fechas tienen su propio capítulo más adelante, con el paquete lubridate.


18.8 Segmentar un archivo

Ahora se pueden juntar las dos mitades del capítulo. Cuando read_csv() lee un archivo, hace con cada columna lo mismo que acabas de hacer a mano: mira los valores, adivina el tipo, y aplica la función parse_*() que corresponde.

guess_parser() contesta la pregunta “¿qué tipo diría readr que es esto?”. Es la manera de entender por qué una columna llegó como texto cuando tú la querías numérica: casi siempre hay una celda con un valor raro escondida en el medio.

guess_parser(c("1", "2", "3"))
## [1] "double"
guess_parser(c("1", "2", "no medido"))
## [1] "character"
guess_parser("2016-08-01")
## [1] "date"
guess_parser("01/08/2016")
## [1] "character"

Cuando la adivinanza no es la que quieres, se lo dices con col_types. Cada función parse_*() tiene una gemela col_*() para usar ahí dentro: col_character(), col_double(), col_integer(), col_date(), col_factor(). Solo hay que nombrar las columnas que quieres cambiar; las demás se siguen adivinando.

En el ejemplo, la edad de la madre se lee como texto (una categoría) en vez de como número:

Newborn_edad <- read_csv("Datos/Newborn_Karn_Penrose.csv",
                         col_types = cols(Mother_age = col_character()))

glimpse(Newborn_edad)
## Rows: 261
## Columns: 4
## $ Mother_age          <chr> "16", "16", "16", "16", "16", "16", "16", "16", "1…
## $ Count_Non_survivors <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1,…
## $ Count_Total_Birth   <dbl> 1, 2, 1, 3, 7, 2, 4, 2, 2, 1, 1, 1, 4, 10, 10, 38,…
## $ Birth_weight        <dbl> 9.5, 9.0, 8.5, 8.0, 7.5, 7.0, 6.5, 6.0, 5.5, 5.0, …

El flujo de trabajo completo para un archivo nuevo es siempre el mismo:

  1. Léelo con read_csv() sin argumentos.
  2. Revísalo con glimpse() y problems().
  3. Si algo llegó mal, vuelve a leerlo añadiendo skip, na o col_types.

Corregir en la lectura es mejor que corregir después, porque el arreglo queda escrito en una sola línea, al principio del script, donde cualquiera lo ve.


18.9 Leer varios archivos a la vez

Es muy común que los datos lleguen repartidos: un archivo por año, por mes o por sitio de muestreo. Si todos tienen las mismas columnas, read_csv() los lee y los apila en una sola tabla: basta con darle un vector de rutas. El argumento id añade una columna que dice de qué archivo salió cada fila.

En Datos/ están los vuelos de San Juan de enero de tres años, en tres archivos:

archivos <- c("Datos/Vuelos_SJU_2018_Ene.csv",
              "Datos/Vuelos_SJU_2019_Ene.csv",
              "Datos/Vuelos_SJU_2020_Ene.csv")

vuelos_sju <- read_csv(archivos, id = "archivo")

vuelos_sju %>%
  count(archivo)
## # A tibble: 3 × 2
##   archivo                           n
##   <chr>                         <int>
## 1 Datos/Vuelos_SJU_2018_Ene.csv  3831
## 2 Datos/Vuelos_SJU_2019_Ene.csv  5128
## 3 Datos/Vuelos_SJU_2020_Ene.csv  5918

Sin la columna archivo no habría forma de saber a qué año pertenece cada vuelo, así que acostúmbrate a usar id siempre que leas más de un archivo.


18.10 Archivos de Excel

Muchos de los datos que vas a recibir en biología llegan en Excel. readr no lee Excel; para eso está el paquete readxl, que se instala con el tidyverse pero hay que cargarlo aparte.

Un archivo de Excel tiene algo que un .csv no tiene: varias hojas. Por eso el primer paso es preguntar cuáles hay. Usamos un archivo de ejemplo que viene dentro del mismo paquete:

library(readxl)

ruta <- readxl_example("datasets.xlsx")
excel_sheets(ruta)
## [1] "mtcars"   "chickwts" "quakes"

Y después se lee la hoja que interesa, por su nombre o por su número:

read_excel(ruta, sheet = "chickwts")
## # A tibble: 71 × 2
##    weight feed     
##     <dbl> <chr>    
##  1    179 horsebean
##  2    160 horsebean
##  3    136 horsebean
##  4    227 horsebean
##  5    217 horsebean
##  6    168 horsebean
##  7    108 horsebean
##  8    124 horsebean
##  9    143 horsebean
## 10    140 horsebean
## # ℹ 61 more rows

El segundo archivo de ejemplo se parece más a un Excel de la vida real: tiene un título arriba de la tabla y notas debajo. Leído sin más, sale un desastre:

ruta2 <- readxl_example("deaths.xlsx")
read_excel(ruta2)
## # A tibble: 18 × 6
##    `Lots of people`             ...2                     ...3  ...4  ...5  ...6 
##    <chr>                        <chr>                    <chr> <chr> <chr> <chr>
##  1 simply cannot resist writing <NA>                     <NA>  <NA>  <NA>  some…
##  2 at                           the                      top   <NA>  of    thei…
##  3 or                           merging                  <NA>  <NA>  <NA>  cells
##  4 Name                         Profession               Age   Has … Date… Date…
##  5 David Bowie                  musician                 69    TRUE  17175 42379
##  6 Carrie Fisher                actor                    60    TRUE  20749 42731
##  7 Chuck Berry                  musician                 90    TRUE  9788  42812
##  8 Bill Paxton                  actor                    61    TRUE  20226 42791
##  9 Prince                       musician                 57    TRUE  21343 42481
## 10 Alan Rickman                 actor                    69    FALSE 16854 42383
## 11 Florence Henderson           actor                    82    TRUE  12464 42698
## 12 Harper Lee                   author                   89    FALSE 9615  42419
## 13 Zsa Zsa Gábor                actor                    99    TRUE  6247  42722
## 14 George Michael               musician                 53    FALSE 23187 42729
## 15 Some                         <NA>                     <NA>  <NA>  <NA>  <NA> 
## 16 <NA>                         also like to write stuff <NA>  <NA>  <NA>  <NA> 
## 17 <NA>                         <NA>                     at t… bott… <NA>  <NA> 
## 18 <NA>                         <NA>                     <NA>  <NA>  <NA>  too!

Se arregla diciéndole a read_excel() en qué rectángulo de celdas está la tabla, con range:

read_excel(ruta2, range = "A5:F15")
## # A tibble: 10 × 6
##    Name      Profession   Age `Has kids` `Date of birth`     `Date of death`    
##    <chr>     <chr>      <dbl> <lgl>      <dttm>              <dttm>             
##  1 David Bo… musician      69 TRUE       1947-01-08 00:00:00 2016-01-10 00:00:00
##  2 Carrie F… actor         60 TRUE       1956-10-21 00:00:00 2016-12-27 00:00:00
##  3 Chuck Be… musician      90 TRUE       1926-10-18 00:00:00 2017-03-18 00:00:00
##  4 Bill Pax… actor         61 TRUE       1955-05-17 00:00:00 2017-02-25 00:00:00
##  5 Prince    musician      57 TRUE       1958-06-07 00:00:00 2016-04-21 00:00:00
##  6 Alan Ric… actor         69 FALSE      1946-02-21 00:00:00 2016-01-14 00:00:00
##  7 Florence… actor         82 TRUE       1934-02-14 00:00:00 2016-11-24 00:00:00
##  8 Harper L… author        89 FALSE      1926-04-28 00:00:00 2016-02-19 00:00:00
##  9 Zsa Zsa … actor         99 TRUE       1917-02-06 00:00:00 2016-12-18 00:00:00
## 10 George M… musician      53 FALSE      1963-06-25 00:00:00 2016-12-25 00:00:00

read_excel() (readxl): leer hojas de Excel (.xls y .xlsx).

  • excel_sheets("archivo.xlsx"): los nombres de las hojas que tiene el archivo.
  • sheet = 2 o sheet = "nombre": cuál hoja leer. Por defecto, la primera.
  • skip = 3: salta filas al principio, igual que en read_csv().
  • range = "A5:F15": lee solo ese rectángulo de celdas.
  • na = "n/d": qué texto tratar como valor faltante.

Con un Excel, el problema casi nunca es leerlo: es que la hoja trae celdas combinadas, un título en la primera fila y notas al pie debajo de la tabla. Abre el archivo en Excel primero, mira en qué celda empieza y en cuál termina la tabla, y usa skip o range. Y cuando el Excel sea tuyo, organízalo desde el principio como una tabla limpia: una fila de nombres, una fila por observación, nada más.


18.11 Datos importados de la web

Los ejemplos de esta sección descargan datos en vivo desde internet, así que necesitas conexión para ejecutarlos. Si ves cannot open URL o un HTTP error, revisa tu conexión o inténtalo más tarde: el servidor puede estar caído o tardar en responder. Una buena práctica es descargar el archivo una vez y guardarlo con write_csv() en Datos/ para no depender de la red. Encontrarás más errores frecuentes y cómo resolverlos en el capítulo Errores comunes (14).

Leer un archivo de la web no necesita ninguna función nueva: read_csv() acepta una dirección de internet en el mismo lugar donde va la ruta. La única condición es que la dirección apunte directamente al archivo .csv, no a una página que lo describe.

18.11.1 Datos de COVID-19 en Puerto Rico

Durante la pandemia se publicaron los datos diarios de Puerto Rico en un repositorio público. El primer paso es leerlos desde la dirección y, en seguida, guardar una copia en Datos/:

url_COVID_PR <- read_csv("https://raw.githubusercontent.com/rafalab/pr-covid/master/dashboard/data/DatosMortalidad.csv")

write_csv(url_COVID_PR, "Datos/url_COVID_PR.csv")

De ahí en adelante se trabaja con la copia, que es lo que hace este libro:

url_COVID_PR <- read_csv("Datos/url_COVID_PR.csv")

glimpse(url_COVID_PR)
## Rows: 587
## Columns: 18
## $ ...1               <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, …
## $ Fecha              <chr> "3/12/20", "3/13/20", "3/14/20", "3/15/20", "3/16/2…
## $ Muertes            <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 5, …
## $ IncrementoMuertes  <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 2, …
## $ CasosPCR_Salud     <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ IncCasosPCR_Salud  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ CasosSaludNuevo    <dbl> 2, 5, 8, 8, 17, 24, 30, 35, 49, 61, 71, 105, 153, 1…
## $ IncCasosSaludNuevo <dbl> 2, 3, 3, 0, 9, 7, 6, 5, 14, 12, 10, 34, 48, 43, 60,…
## $ HospitCOV19        <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ CamasICU           <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ CamasICU_disp      <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ Ventiladores       <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ MuertesSalud       <dbl> 0, 0, 0, 0, 0, 1, 1, 2, 2, 4, 5, 7, 8, 8, 11, 13, 1…
## $ IncMueSalud        <dbl> 0, 0, 0, 0, 0, 1, 0, 1, 0, 2, 1, 2, 1, 0, 3, 2, 3, …
## $ VacDoses           <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ VacAdm             <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ N1MoreDoses        <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ N2Doses            <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…

Al revisarla, como siempre, aparecen dos cosas que atender:

  • La primera columna se llama ...1. La copia que acompaña al libro traía una columna de números de fila sin nombre, y read_csv() le inventó uno. Es la huella típica de un archivo guardado con write.csv() de base R, que añade esos números por su cuenta; write_csv() no lo hace.
  • Fecha llegó como texto (<chr>), porque está escrita como mes/día/año con dos cifras. Mientras sea texto no se puede ordenar ni graficar en el tiempo.

Las dos se arreglan con lo que ya sabes:

url_COVID_PR <- url_COVID_PR %>%
  dplyr::select(-1) %>%
  mutate(Fecha = parse_date(Fecha, format = "%m/%d/%y"))

range(url_COVID_PR$Fecha)
## [1] "2020-03-12" "2021-10-19"

Ahora sí se puede analizar. Por ejemplo, el promedio de muertes por día en Puerto Rico y el máximo de muertes en un día por COVID:

 url_COVID_PR %>%
  dplyr::select(IncrementoMuertes) %>%
  summarise(Prom_muerte_dia = mean(IncrementoMuertes, na.rm = TRUE),
            maximo_muerte_dia = max(IncrementoMuertes, na.rm = TRUE))
## # A tibble: 1 × 2
##   Prom_muerte_dia maximo_muerte_dia
##             <dbl>             <dbl>
## 1            5.47                29

Y, como Fecha ya es una fecha de verdad, la gráfica en el tiempo sale sola:

url_COVID_PR %>%
  ggplot(aes(Fecha, IncrementoMuertes)) +
  geom_line()

18.11.2 Dónde encontrar datos de Puerto Rico

Este sitio del gobierno de Puerto Rico almacena datos de muchos tipos diferentes que están relacionados a la isla:

https://datos.estadisticas.pr/dataset?res_format=CSV

  • Cuando entre a la página, selecciona uno de los archivos, por ejemplo Facultad - Conservatorio de Música
  • Presione el botón “Explore” y selecciona “Preview”
  • Arriba de la página verá el enlace de https://……… que termina en .csv
  • Copie el enlace y péguelo, entre comillas, dentro de read_csv()

Otra alternativa es bajar los datos en formato .csv o Excel con el navegador, mover el archivo a la carpeta Datos/ de su proyecto en RStudio, y leerlo de ahí como cualquier otro archivo.


18.12 Escribir un Archivo

Guardar es tan importante como leer, y por dos razones distintas: para no depender de la red, y para dejarle a otra persona (o a un revisor) el resultado de tu limpieza sin que tenga que correr todo tu script.

  • write_csv(tabla, "Datos/archivo.csv"): separado por comas
  • write_tsv(tabla, "Datos/archivo.tsv"): separado por tabuladores

El primer argumento es la tabla y el segundo la ruta donde se guarda. Por ejemplo, para guardar la tabla de COVID ya limpia, con la fecha convertida:

write_csv(url_COVID_PR, "Datos/covid_pr_limpio.csv")

Guarda el resultado con un nombre nuevo. El archivo original, tal como lo recibiste, no se toca nunca: es la única prueba de cómo eran los datos antes de tu limpieza.

Un .csv no guarda los tipos: al volver a leerlo, readr tiene que adivinar otra vez, y una columna que tú habías convertido en factor vuelve a ser texto. Si necesitas conservar los tipos exactamente, guarda con write_rds() y vuelve a leer con read_rds() (formato propio de R), o usa arrow::write_parquet(). Para compartir con gente que no usa R, el .csv sigue siendo la mejor opción.

write_rds(url_COVID_PR, "Datos/covid_pr_limpio.rds")

covid <- read_rds("Datos/covid_pr_limpio.rds")   # vuelve con los mismos tipos

18.13 Otros tipos de datos y paquetes

R lee casi cualquier formato, siempre con la misma idea: un paquete por familia de archivos.

Paquetes para otros formatos: cuál usar según de dónde vengan los datos.

  • readxl: hojas de Excel, con read_excel(), como viste arriba.
  • haven: archivos de SPSS (.sav), Stata (.dta) y SAS. Es el camino habitual cuando los datos vienen de una encuesta o de otra disciplina.
  • DBI y RMariaDB / RPostgres: bases de datos, cuando la tabla es demasiado grande para caber en la memoria.
  • googlesheets4: hojas de Google, útiles cuando varias personas están llenando la tabla a la vez.

18.14 Ejercicios del libro R4DS

Hacer los ejercicios de las siguientes secciones del libro en español:

  • sección 11.2.2 (leer archivos),
  • sección 11.3.5 (segmentar vectores).

18.15 Ejercicios del capítulo

Sobre los ejercicios de este capítulo. Cada ejercicio se entrega con tres cosas: el script, el resultado (que el bloque corra) y una explicación en palabras. Los ejercicios usan Datos/crimen_pr.csv, un archivo real del gobierno de Puerto Rico que está en el repositorio del curso, y archivos que vas a crear tú mismo. No usan Newborn_Karn_Penrose.csv, que es el del capítulo. La hoja completa para entregar está en Ejercicios/Ejercicios_Capitulo_18_importar.Rmd.

18.15.1 Ejercicio 1. Mirar antes de calcular

Lee Datos/crimen_pr.csv con read_csv(). Antes de tocar nada, mira qué llegó: imprime el tibble, corre glimpse() y spec(), y cuenta los valores faltantes de cada columna.

Verificación: la tabla debe tener 8870 filas y 7 columnas, y dos de las columnas deben traer 733 valores faltantes cada una.

En palabras: readr convirtió esos faltantes solo porque en el archivo estaban escritos como NA. ¿Qué habría pasado si el archivo los hubiera escrito como "sin dato"? ¿Con cuál argumento lo arreglarías?

# Escribe tu código aquí

18.15.2 Ejercicio 2. Los nombres mienten

Sigue con la misma tabla. Mira los valores de Delito y de Delitos_code con count().

Verificación: cada una tiene 9 valores distintos, pero Delito contiene números del 1 al 9 y Delitos_code contiene nombres como Asesinato o Apropiacion Ilegal. Los nombres de las dos columnas están intercambiados.

Renómbralas con rename() para que digan la verdad, y de paso quítale el espacio al nombre de Area Policiaca.

En palabras: para usar Area Policiaca tal como viene hay que escribirla entre acentos graves. ¿Por qué? ¿Qué le habría hecho read.csv() a ese nombre?

# Escribe tu código aquí

18.15.3 Ejercicio 3. Un código no es un número

La columna con los números del 1 al 9 es un código, no una cantidad: no tiene sentido calcular su promedio. Vuelve a leer el archivo obligando a que esa columna se lea como texto, con col_types = cols(...) y col_character().

Verificación: class() de esa columna debe pasar de "numeric" a "character", y el resto de la tabla debe quedar igual (8870 por 7).

En palabras: da otro ejemplo de una columna que parece numérica y no lo es. ¿Qué se pierde si un código postal de Puerto Rico se lee como número?

# Escribe tu código aquí

18.15.4 Ejercicio 4. Ida y vuelta por el disco

Toma millas del paquete datos. Convierte clase en factor con mutate(clase = factor(clase)), guarda la tabla con write_csv() en Datos/millas_prueba.csv, y vuelve a leerla con read_csv().

Verificación: la tabla que vuelve tiene las mismas 234 filas, pero clase ya no es factor: es texto.

En palabras: explica por qué se perdió el tipo. ¿Qué formato usarías si necesitaras conservarlo? ¿Y por qué, aun así, seguimos usando .csv para compartir datos?

# Escribe tu código aquí

18.15.5 Ejercicio 5. Punto y coma, y coma decimal

Crea con writeLines() el archivo Datos/hojas_sucio.csv con estas cuatro líneas. Así lo exportaría un Excel configurado en español: sin fila de nombres, con punto y coma entre las columnas, con coma decimal, y con s/d donde no hay dato:

H01;12,5;3

H02;s/d;0

H03;9,75;5

H04;13,75;2

Léelo primero con read_csv() sin argumentos y mira el desastre. Después léelo bien: escoge la función de la familia de readr que corresponde a este formato, ponle a las columnas los nombres hoja, peso y manchas, y declara el faltante.

Verificación: la versión arreglada tiene 4 filas y 3 columnas, peso es numérica, hay exactamente un NA, y el promedio de peso (sin contar el faltante) es 12.

En palabras: en la primera lectura, ¿cuál signo tomó read_csv() como separador de columnas, y por qué eso partió los números por la mitad? ¿Qué le pasó a la hoja H01? Si el promedio te sale distinto de 12, ¿cuál de los tres problemas del archivo te faltó resolver?

# Escribe tu código aquí

18.15.6 Ejercicio 6. Un archivo por grupo

Los datos muchas veces llegan repartidos en varios archivos. Aquí los vas a repartir tú, para después juntarlos. De millas (paquete datos), guarda con filter() y write_csv() tres archivos, uno por fabricante: Datos/millas_audi.csv, Datos/millas_honda.csv y Datos/millas_toyota.csv.

Después léelos los tres a la vez, con una sola llamada a read_csv(), y pide una columna archivo que diga de dónde salió cada fila. Cuenta las filas por archivo.

Verificación: la tabla unida tiene 61 filas: 18 de Audi, 9 de Honda y 34 de Toyota. Tiene además una columna más que millas.

En palabras: en esta tabla hay otra columna que dice lo mismo que archivo. ¿Cuál es? Ahora piensa en datos de campo guardados en un archivo por año, o uno por sitio de muestreo, donde esa información solo está en el nombre del archivo. ¿Qué se perdería al juntarlos sin la columna archivo?

# Escribe tu código aquí

18.15.7 Ejercicio 7 (BONO). Fechas y horas de verdad

Vuelve a crimen_pr.csv. Comprueba con class() cómo llegaron Fecha y Hora. Después cuenta cuántos incidentes hay por mes.

Verificación: hay tres meses, todos de 2016, y el tercero tiene muchísimos menos casos que los otros dos.

En palabras: ¿por qué el tercer mes tiene tan pocos? Míralo con range(Fecha) y explica qué te dice eso sobre el archivo. ¿Qué error de análisis cometería alguien que comparara los tres meses sin darse cuenta?

# Escribe tu código aquí