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()deread.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:
- De un archivo tuyo, dentro de la carpeta del proyecto. Es lo más común y lo más reproducible.
- 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. - 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:
## # 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 comaread_csv2(): separado por punto y coma (común en archivos de países que usan la coma como decimal)read_tsv(): separado por tabuladorread_delim(): separado por el delimitador que tú le digas, con el argumentodelimread_table(): columnas separadas por espacios en blancoread_fwf(): columnas de ancho fijo, donde cada columna ocupa siempre las mismas posiciones; los anchos se le indican confwf_widths()ofwf_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 concomment = "#". - 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/dy un punto. Se declaran conna. - Columna B: compara con el archivo de texto. Los códigos eran
00791,00725…, y Excel los muestra como791,725…: decidió que eran números y les quitó los ceros. Es justo el problema quecol_typesevita 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:
## # 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:
## # 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:
## # 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:
## # A tibble: 2 × 3
## X1 X2 X3
## <dbl> <dbl> <dbl>
## 1 1 2 3
## 2 4 5 6
## # 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:
## # 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.
## # 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:
## # 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”:
## # 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:
- ¿Llegaron todas las filas y todas las columnas?
- ¿Cada columna tiene el tipo que debe tener?
- ¿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.
## 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.
## 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:
## 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 quereadrle asignó a cada columna, listo para copiar encol_types.problems(tabla): la lista de las celdas quereadrno 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.
## [1] "TRUE" "FALSE" "NA"
## logi [1:3] TRUE FALSE NA
## [1] "Carlos" "Kelvin"
## 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"
## [1] "1" "2" "3"
## [1] NA
## int [1:3] 1 2 3
## [1] 2
## 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ó.
## [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
## # 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.
## [1] 4000 500
## [1] 100
## [1] 20
## [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:
## [1] 1.5
## [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
## [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.
## [1] 52 61 6d 6f 6e
## [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"
## [1] "こんにちは"
Y cuando no sabes cuál es la codificación, guess_encoding() intenta adivinarla:
## # A tibble: 2 × 2
## encoding confidence
## <chr> <dbl>
## 1 ISO-8859-1 0.46
## 2 ISO-8859-9 0.23
## # 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] "Ρ̓αμόν"
## [1] "Рамо́н"
## [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:
## [1] 5
## [1] 6
## [1] 6
## [1] 12
## [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:
## [1] 4e 41
## [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.
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.
## [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
## # 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 fechaparse_datetime(): una fecha con su horaparse_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.
## [1] "2016-08-01"
## [1] "2016-08-01"
## [1] "2015-01-02"
## 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.
## [1] "double"
## [1] "character"
## [1] "date"
## [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:
- Léelo con
read_csv()sin argumentos. - Revísalo con
glimpse()yproblems(). - Si algo llegó mal, vuelve a leerlo añadiendo
skip,naocol_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:
## [1] "mtcars" "chickwts" "quakes"
Y después se lee la hoja que interesa, por su nombre o por su número:
## # 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:
## # 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:
## # 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 = 2osheet = "nombre": cuál hoja leer. Por defecto, la primera.skip = 3: salta filas al principio, igual que enread_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:
## 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, yread_csv()le inventó uno. Es la huella típica de un archivo guardado conwrite.csv()de base R, que añade esos números por su cuenta;write_csv()no lo hace. Fechallegó 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:

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 comaswrite_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:
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.
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, conread_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.DBIyRMariaDB/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?
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?
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?
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?
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?
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?
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?