library(tidyverse) #siempre lo tenemos a mano
library(stringr) #para trabajar con strings
library(lubridate) #para trabajar con fechas
El paquete lubridate está pensado para trabajar con los datos tipo fecha(date) o fecha-hora(datetime) para cambiarles el formato, realizar operaciones y extraer información.
La cheatsheet muestra todas las cosas que se pueden hacer con este paquete
Existe una gran cantidad de funciones para realizar esto. La idea general es poder llevar los objetos datetime a un formato común compuesto de los elementos: año, mes, día, hora, minuto y segundo (también se puede setear el huso horario)
fecha <- "29/09/93 17:35:16"
fecha
[1] "29/09/93 17:35:16"
# Clase de dato
class(fecha)
[1] "character"
Con la función dmy_hms podemos convertir este string a una fecha: estamos indicando que el formato de la fecha es día(d), mes(m), año(y), hora(h), minuto(m) y segundo(s)
fecha <- dmy_hms(fecha)
fecha
[1] "1993-09-29 17:35:16 UTC"
# Clase de dato
class(fecha)
[1] "POSIXct" "POSIXt"
Muchas funciones de lubridate operan con esta misma lógica
Otra función para realizar un cambio de formato es parse_date_time. Permite construir objetos datetime a partir de datos más complejos, como por ejemplo cuando aparece el nombre del mes y el año.
En el parámetro x pasamos el dato de la fecha y en el parámetro orders especificamos el orden en el cual se encuentra la información de la fecha
fecha2 <- "Sep-93"
fecha2 <- parse_date_time(fecha2, orders = 'my')
fecha2
Existen muchas funciones muy sencillas para extraer información de un objeto datetime. Algunas son
# Obtener el año
year(fecha)
#Obtener el semestre
semester(fecha)
#Obtener el trimestre
quarter(fecha)
#Obtener el mes
month(fecha)
# Obtener el día
day(fecha)
# Obtener la fecha
date(fecha)
# Obtener el dia de la semana
wday(fecha)
#Obtener el nombre del día
wday(fecha, label = TRUE)
#Obtener la hora
hour(fecha)
Podemos sumar o restarle cualquier período de tiempo a un objeto datetime
# Sumo dos días
fecha + days(2)
# Resto 1 semana y dos horas
fecha - (weeks(1) + hours(2))
Vamos a usar un paquete muy útil para manejar variables de tipo string, es decir, variables de texto, llamado stringr. Primero vamos a ver algunos de los principales comandos y luego vamos a ver algunos ejemplos un poco más complejos de cómo podemos usar las funciones de este paquete.
Tal como vimos para ggplot2, el paquete stringr cuenta con una cheat sheet que pueden consultar siguiendo este link.
Con la función str_length() podemos ver el largo de un string.
string1 <- "abcdefghi"
str_length(string1)
Ojo que cuenta los espacios en blanco como un caracter.
string2 <- "abcd efghi"
str_length(string2)
La función str_sub() nos permite extraer los caracteres que se encuentran entre determinadas posiciones. Tiene tres argumentos: el string, el orden del caracter a partir del cual tiene que empezar a extraer y el orden del caracter hasta el cual tiene que extraer.
#quiero el tercer caracter
str_sub(string1,3,3)
#quiero el cuarto y quinto caracter
str_sub(string1,4,5)
Puedo pasarle la posición de los caracteres con un menos para indicar que quiero que cuente de atrás para adelante. Por ejemplo, si quiero que traiga el anteúltimo caracter llamo a la posición como -2.
#quiero la última y anteúltima posición
str_sub(string1,-2,-1)
Otro uso que le podemos dar a este comando es el de reemplazar elementos. Supongamos que quiero reemplazar la última letra por una z.
str_sub(string1,-1,-1) <- "z"
string1
Es frecuente que aparezcan datos mal cargados o con errores de tipeo que tienen espacios donde no debería haberlos. La función str_trim() permite que nos deshagamos de los espacios en blanco a izquierda, derecha o ambos lados de nuestro string.
string3 <- c(" acelga ", "brocoli ", " choclo")
#Veamos el string
string3
Quitamos los espacios en blanco a ambos lados con el argumento side=‘both’
str_trim(string3, side = 'both')
Quitamos los espacios en blanco del lado izquierdo con el argumento side=‘left’
str_trim(string3, "left")
Existen varias funciones para manipular las mayusculas/minúsculas de nuestros strings. A modo de sugerencia, siempre es convientiente manejarse con todos los caracteres en minúscula o en mayúscula. Esto ayuda a normalizar la información para armar mejor grupos, joinear tablas, etc.
string4 <- "No me gusta el frio"
string4
#llevo todo a minúsculas
str_to_lower(string4)
#llevo todo a mayúsculas
str_to_upper(string4)
#llevo a mayúscula la primer letra de cada palabra
str_to_title(string4)
La función str_split() nos permite partir un string de acuerdo a algún separador/patron (pattern) que definamos.
#quiero separar todas las letras de mi string
string1_separado <- str_split(string1,pattern = "")
string1_separado
Notemos que esta funcion nos devuelve una lista
class(string1_separado)
# Si queremos acceder al primer elemento de la lista
string1_separado[[1]][1]
Definimos un nuevo string
string5 <- "ab-cd-ef"
string5
Lo separamos por el guion
str_split(string5,pattern="-")
La función base gsub() nos permite reemplazar parte del string por otra cosa. Las funciones str_replace() y str_replace_all() de stringr nos permiten hacer lo mismo.
str_replace solo reemplaza la primera ocurrencia del patron mientras que str_replace_all() reemplaza todas las ocurrencias
string6 <- "todos los caballos blancos"
string6
#le sacamos los espacios
gsub(pattern = " ", replacement = "", x = string6)
#lo llevamos a lenguaje inclusivo
gsub(pattern = "os", replacement = "xs", x = string6)
Con stringr
# Reemplazamos la primera ocurrencia
str_replace(string = string6, pattern = "os", replacement = "xs")
# Reemplazamos todas las ocurrencias
str_replace_all(string = string6, pattern = "os", replacement = "xs")
La función grepl() nos permite encontrar expresiones dentro de nuestros strings. Nos reporta VERDADERO o FALSO de acuerdo a si encuentra la expresión que estamos buscando.
string7 <-c("caño", "baño", "ladrillo")
string7
grepl(pattern = "ñ",x = string7)
La funcion str_detect() de stringr permite hacer lo mismo.
Vamos a ver un ejemplo a partir de una base pública que nos trae información sobre ataques de tiburones en el mundo en distintos momentos. Cada fila corresponde a un ataque.
tiburones <- read.csv("https://raw.githubusercontent.com/dmuba/dmuba.github.io/3005a7a5e8c9db5c188ca22a7b9269c94db8ced7/Practicos/TPs%20Entregables/TP02/Shark_Attack_Data.csv") %>% select(-c(Case.Number.1,Case.Number.2,original.order, X, X.1))
head(tiburones,10)
Vamos a trabajar un poco con la limpieza y la normalización de esta base ya que al juntar datos de distintas fuentes la información no es homogénea por lo que es dificil hacer cualquier tipo de análisis.
Veamos si existen problemas con la variable del país (Country)
# Creamos una lista con los valores de Country para ver si hay algunos problemas
tiburones %>% select(Country) %>% # Seleccionamos la variable del pais
mutate(Country=as.character(Country)) %>% # Lo convertimos de factor a string
arrange(Country) %>% # La ordenamos
distinct() %>% # Nos quedamos con los valores unicos
as.list() # La convertimos en lista para que sea mas sencilla de ver
$Country
[1] "" " PHILIPPINES" " TONGA"
[4] "ADMIRALTY ISLANDS" "ALGERIA" "AMERICAN SAMOA"
[7] "ANDAMAN / NICOBAR ISLANDAS" "ANGOLA" "ANTIGUA"
[10] "ARGENTINA" "ARUBA" "ASIA?"
[13] "ATLANTIC OCEAN" "AUSTRALIA" "AZORES"
[16] "BAHAMAS" "BAHREIN" "BANGLADESH"
[19] "BARBADOS" "BAY OF BENGAL" "BELIZE"
[22] "BERMUDA" "Between PORTUGAL & INDIA" "BRAZIL"
[25] "BRITISH ISLES" "BRITISH NEW GUINEA" "BRITISH VIRGIN ISLANDS"
[28] "BRITISH WEST INDIES" "BURMA" "CANADA"
[31] "CAPE VERDE" "CARIBBEAN SEA" "CAYMAN ISLANDS"
[34] "CENTRAL PACIFIC" "CEYLON (SRI LANKA)" "CHILE"
[37] "CHINA" "Coast of AFRICA" "COLUMBIA"
[40] "COOK ISLANDS" "COSTA RICA" "CRETE"
[43] "CROATIA" "CUBA" "CURACAO"
[46] "CYPRUS" "DIEGO GARCIA" "DJIBOUTI"
[49] "DOMINICAN REPUBLIC" "ECUADOR" "EGYPT"
[52] "EGYPT " "EGYPT / ISRAEL" "EL SALVADOR"
[55] "ENGLAND" "EQUATORIAL GUINEA / CAMEROON" "FALKLAND ISLANDS"
[58] "FEDERATED STATES OF MICRONESIA" "Fiji" "FIJI"
[61] "FRANCE" "FRENCH POLYNESIA" "GABON"
[64] "GEORGIA" "GHANA" "GRAND CAYMAN"
[67] "GREECE" "GREENLAND" "GRENADA"
[70] "GUAM" "GUATEMALA" "GUINEA"
[73] "GULF OF ADEN" "GUYANA" "HAITI"
[76] "HONDURAS" "HONG KONG" "ICELAND"
[79] "INDIA" "INDIAN OCEAN" "INDIAN OCEAN?"
[82] "INDONESIA" "IRAN" "IRAN / IRAQ"
[85] "IRAQ" "IRELAND" "ISRAEL"
[88] "ITALY" "ITALY / CROATIA" "JAMAICA"
[91] "JAPAN" "JAVA" "JOHNSTON ISLAND"
[94] "KENYA" "KIRIBATI" "KOREA"
[97] "KUWAIT" "LEBANON" "LIBERIA"
[100] "LIBYA" "MADAGASCAR" "MALAYSIA"
[103] "MALDIVE ISLANDS" "MALTA" "MARSHALL ISLANDS"
[106] "MARTINIQUE" "MAURITIUS" "MAYOTTE"
[109] "MEDITERRANEAN SEA" "MEDITERRANEAN SEA?" "MEXICO"
[112] "MEXICO " "MICRONESIA" "MID ATLANTIC OCEAN"
[115] "MID-PACIFC OCEAN" "MONACO" "MONTENEGRO"
[118] "MOZAMBIQUE" "NAMIBIA" "NETHERLANDS ANTILLES"
[121] "NEVIS" "NEW BRITAIN" "NEW CALEDONIA"
[124] "NEW GUINEA" "NEW ZEALAND" "NICARAGUA"
[127] "NICARAGUA " "NIGERIA" "NORTH ATLANTIC OCEAN"
[130] "NORTH ATLANTIC OCEAN " "NORTH PACIFIC OCEAN" "NORTH SEA"
[133] "NORTHERN ARABIAN SEA" "NORTHERN MARIANA ISLANDS" "NORWAY"
[136] "OCEAN" "OKINAWA" "PACIFIC OCEAN"
[139] "PACIFIC OCEAN " "PALAU" "PALESTINIAN TERRITORIES"
[142] "PANAMA" "PAPUA NEW GUINEA" "PARAGUAY"
[145] "PERSIAN GULF" "PHILIPPINES" "PORTUGAL"
[148] "PUERTO RICO" "RED SEA" "RED SEA / INDIAN OCEAN"
[151] "RED SEA?" "REUNION" "RUSSIA"
[154] "SAMOA" "SAN DOMINGO" "SAUDI ARABIA"
[157] "SCOTLAND" "SENEGAL" "Seychelles"
[160] "SEYCHELLES" "Sierra Leone" "SIERRA LEONE"
[163] "SINGAPORE" "SLOVENIA" "SOLOMON ISLANDS"
[166] "SOLOMON ISLANDS / VANUATU" "SOMALIA" "SOUTH AFRICA"
[169] "SOUTH ATLANTIC OCEAN" "SOUTH CHINA SEA" "SOUTH KOREA"
[172] "SOUTH PACIFIC OCEAN" "SOUTHWEST PACIFIC OCEAN" "SPAIN"
[175] "SRI LANKA" "St Helena" "ST. MAARTIN"
[178] "ST. MARTIN" "SUDAN" "SUDAN?"
[181] "SWEDEN" "SYRIA" "TAIWAN"
[184] "TANZANIA" "TASMAN SEA" "THAILAND"
[187] "THE BALKANS" "TONGA" "TRINIDAD & TOBAGO"
[190] "TUNISIA" "TURKEY" "TURKS & CAICOS"
[193] "TUVALU" "UNITED ARAB EMIRATES" "UNITED ARAB EMIRATES (UAE)"
[196] "UNITED KINGDOM" "URUGUAY" "USA"
[199] "VANUATU" "VENEZUELA" "VIETNAM"
[202] "WESTERN SAMOA" "YEMEN" "YEMEN "
[205] "YUGOSLAVIA"
Hay varios problemas, pero veamos dos:
Llevamos los nombres a mayúscula
tiburones <- tiburones %>% mutate(pais=str_to_upper(Country))
head(tiburones, 10)
Quitamos los espacios en blanco a izquierda y derecha
tiburones <- tiburones %>% mutate(pais=str_trim(pais, side="both"))
tiburones %>% select(pais) %>% # Seleccionamos la variable del pais
#mutate(Country=as.character(Country)) %>% # Lo convertimos de factor a string
arrange(pais) %>% # La ordenamos
distinct() %>% # Nos quedamos con los valores unicos
as.list() # La convertimos en lista para que sea mas sencilla de ver
$pais
[1] "" "ADMIRALTY ISLANDS" "ALGERIA"
[4] "AMERICAN SAMOA" "ANDAMAN / NICOBAR ISLANDAS" "ANGOLA"
[7] "ANTIGUA" "ARGENTINA" "ARUBA"
[10] "ASIA?" "ATLANTIC OCEAN" "AUSTRALIA"
[13] "AZORES" "BAHAMAS" "BAHREIN"
[16] "BANGLADESH" "BARBADOS" "BAY OF BENGAL"
[19] "BELIZE" "BERMUDA" "BETWEEN PORTUGAL & INDIA"
[22] "BRAZIL" "BRITISH ISLES" "BRITISH NEW GUINEA"
[25] "BRITISH VIRGIN ISLANDS" "BRITISH WEST INDIES" "BURMA"
[28] "CANADA" "CAPE VERDE" "CARIBBEAN SEA"
[31] "CAYMAN ISLANDS" "CENTRAL PACIFIC" "CEYLON (SRI LANKA)"
[34] "CHILE" "CHINA" "COAST OF AFRICA"
[37] "COLUMBIA" "COOK ISLANDS" "COSTA RICA"
[40] "CRETE" "CROATIA" "CUBA"
[43] "CURACAO" "CYPRUS" "DIEGO GARCIA"
[46] "DJIBOUTI" "DOMINICAN REPUBLIC" "ECUADOR"
[49] "EGYPT" "EGYPT / ISRAEL" "EL SALVADOR"
[52] "ENGLAND" "EQUATORIAL GUINEA / CAMEROON" "FALKLAND ISLANDS"
[55] "FEDERATED STATES OF MICRONESIA" "FIJI" "FRANCE"
[58] "FRENCH POLYNESIA" "GABON" "GEORGIA"
[61] "GHANA" "GRAND CAYMAN" "GREECE"
[64] "GREENLAND" "GRENADA" "GUAM"
[67] "GUATEMALA" "GUINEA" "GULF OF ADEN"
[70] "GUYANA" "HAITI" "HONDURAS"
[73] "HONG KONG" "ICELAND" "INDIA"
[76] "INDIAN OCEAN" "INDIAN OCEAN?" "INDONESIA"
[79] "IRAN" "IRAN / IRAQ" "IRAQ"
[82] "IRELAND" "ISRAEL" "ITALY"
[85] "ITALY / CROATIA" "JAMAICA" "JAPAN"
[88] "JAVA" "JOHNSTON ISLAND" "KENYA"
[91] "KIRIBATI" "KOREA" "KUWAIT"
[94] "LEBANON" "LIBERIA" "LIBYA"
[97] "MADAGASCAR" "MALAYSIA" "MALDIVE ISLANDS"
[100] "MALTA" "MARSHALL ISLANDS" "MARTINIQUE"
[103] "MAURITIUS" "MAYOTTE" "MEDITERRANEAN SEA"
[106] "MEDITERRANEAN SEA?" "MEXICO" "MICRONESIA"
[109] "MID ATLANTIC OCEAN" "MID-PACIFC OCEAN" "MONACO"
[112] "MONTENEGRO" "MOZAMBIQUE" "NAMIBIA"
[115] "NETHERLANDS ANTILLES" "NEVIS" "NEW BRITAIN"
[118] "NEW CALEDONIA" "NEW GUINEA" "NEW ZEALAND"
[121] "NICARAGUA" "NIGERIA" "NORTH ATLANTIC OCEAN"
[124] "NORTH PACIFIC OCEAN" "NORTH SEA" "NORTHERN ARABIAN SEA"
[127] "NORTHERN MARIANA ISLANDS" "NORWAY" "OCEAN"
[130] "OKINAWA" "PACIFIC OCEAN" "PALAU"
[133] "PALESTINIAN TERRITORIES" "PANAMA" "PAPUA NEW GUINEA"
[136] "PARAGUAY" "PERSIAN GULF" "PHILIPPINES"
[139] "PORTUGAL" "PUERTO RICO" "RED SEA"
[142] "RED SEA / INDIAN OCEAN" "RED SEA?" "REUNION"
[145] "RUSSIA" "SAMOA" "SAN DOMINGO"
[148] "SAUDI ARABIA" "SCOTLAND" "SENEGAL"
[151] "SEYCHELLES" "SIERRA LEONE" "SINGAPORE"
[154] "SLOVENIA" "SOLOMON ISLANDS" "SOLOMON ISLANDS / VANUATU"
[157] "SOMALIA" "SOUTH AFRICA" "SOUTH ATLANTIC OCEAN"
[160] "SOUTH CHINA SEA" "SOUTH KOREA" "SOUTH PACIFIC OCEAN"
[163] "SOUTHWEST PACIFIC OCEAN" "SPAIN" "SRI LANKA"
[166] "ST HELENA" "ST. MAARTIN" "ST. MARTIN"
[169] "SUDAN" "SUDAN?" "SWEDEN"
[172] "SYRIA" "TAIWAN" "TANZANIA"
[175] "TASMAN SEA" "THAILAND" "THE BALKANS"
[178] "TONGA" "TRINIDAD & TOBAGO" "TUNISIA"
[181] "TURKEY" "TURKS & CAICOS" "TUVALU"
[184] "UNITED ARAB EMIRATES" "UNITED ARAB EMIRATES (UAE)" "UNITED KINGDOM"
[187] "URUGUAY" "USA" "VANUATU"
[190] "VENEZUELA" "VIETNAM" "WESTERN SAMOA"
[193] "YEMEN" "YUGOSLAVIA"
Vamos a empezar analizando la variable Date de nuestro dataset. Primero llevamos esta variable a un pequeño dataset aparte y llevamos la variable al tipo character para poder manejarla como un string.
Date<-tiburones %>% select(Case.Number, Date) %>% # Seleccionamos las variables de numero de caso y fecha
mutate(Date=as.character(Date)) # Tranformamos la fecha en string
glimpse(Date)
Vemos que la variable Date viene en muchos casos con la palabra Reported lo cual nos impide que podamos tratar esa expresión como fecha usando las herramientas que vimos en la sección de lubridate.
Date <- Date %>% mutate(date_clean1= str_replace_all(string = Date, pattern = "Reported ", replacement = ""))
head(Date,10)
Vamos ahora a ver el caso de la variable Time. Vamos a proceder igual que con la variable Date, creando un pequeño dataset para normalizar esta variable.
#traemos la variable que nos interesa
Time <- tiburones %>% select(Time)
#la llevamos a tipo character para trabajarla como string
Time <- Time %>% mutate(Time=as.character(Time))
#borro esta entrada porque tiene un dato raro que no nos deja trabajar
Time[1545,] <- ""
#llevamos todo a minúsculas (con otra función que también hace eso)
Time<-Time %>% mutate(Time=tolower(Time))
Time
Nuestro objetivo es llevar las horas a una variable categórica con los valores mañana, tarde y noche. Para eso por un lado vamos a tener que extraer la hora de las variables que la tienen cargada en forma númerica para luego agruparlas en esas tres categorías, y por el otro lado normalizar la forma categórica dada en inglés que actualmente tiene cuatro categorías.
Empecemos trabajando con las entradas que tienen los valores dados en números. Primero vamos a ver que entradas tienen h y vamos a generar una columna con el largo del string.
Time <- Time %>% mutate(tiene_h = grepl(pattern = "h",x = Time),
largo=str_length(Time))
Time
Ahora creamos una variable que nos va a decir si se trata de un caso con la hora dada en números. Para detectar estos casos, el string tiene que contener la letra h y tener 5 caracteres.
Time = Time %>% mutate(es_hora = case_when(tiene_h==TRUE & largo==5 ~ "si",
tiene_h==FALSE | largo!=5 ~ "no"))
Time
Ahora, si se trata de una entrada con el formato de hora, vamos a traer la primera y segunda posición que corresponden a la hora.
Time = Time %>% mutate(hora = case_when(es_hora =="si" ~ substr(Time,1,2)),
hora = as.numeric(hora))
Time
Ahora vamos a llevar los valores noméricos continuos a las variables discretas que mencionamos y vamos a llevar las variables en inglés a las variables en castellano.
Time = Time %>% mutate(hora_estandar = case_when(grepl("afternoon",Time)==TRUE ~ "tarde",
grepl("day",Time)==TRUE ~ "tarde",
grepl("night",Time)==TRUE ~ "noche",
grepl("morning",Time)==TRUE ~ "mañana",
hora>5 & hora<12 ~ "mañana",
hora>11 & hora<19 ~ "tarde",
hora<6 | hora>18 ~ "noche"))
# Reemplazamos los NA por "hora indefinida"
Time <- Time %>% replace_na(list(hora_estandar="hora indefinida"))
Time
# Seleccionamos algunas variables
tiburones <- tiburones %>% select(Case.Number, Type,Date, pais)
# Hacemos la limpieza de la fecha como vimos más arriba
tiburones <- tiburones %>% mutate(date_clean1= str_replace_all(string = Date, pattern = "Reported ", replacement = ""))
glimpse(tiburones)
Transformamos la variable fecha de un string a una fecha
tiburones <- tiburones %>% mutate(fecha= dmy(date_clean1))
El mensaje indica que no pudo convertir a fecha 889 registros.
Extraemos variables de la fecha
tiburones <- tiburones %>% mutate(año = year(fecha), #Obtener el año
semestre=semester(fecha), # Obtener el semestre
trimestre=quarter(fecha),#Obtener el trimestre
mes=month(fecha,label = TRUE), #Obtener el nombre del mes
dia=day(fecha),# Obtener el día
dia_semana=wday(fecha, label = TRUE) #Obtener el nombre del día
)
glimpse(tiburones)
Filtramos los registros y nos quedamos con los ataques desde 1800
tiburones_filtro <- tiburones %>% filter(año >=1800)
Veamos la evolución por año de los ataques de tiburones
ggplot(tiburones_filtro, aes(año)) +
geom_line(stat = "count") +
geom_point(stat='count') +
theme_bw() +
labs(title='Evolucion ataques de tiburones', x='Año', y='Ataques')
Seleccionamos a dos paises con muchos ataques. Recordemos que habiamos hecho algunos procedimientos de normalización sobre esta variable
tiburones_aus_usa <- tiburones_filtro %>% filter(pais %in% c("AUSTRALIA","USA"))
Observemos el ciclo anual de ataques de tiburones en estos paises
ggplot(tiburones_aus_usa, aes(mes,color=pais, group=pais)) +
geom_line(stat = "count") +
geom_point(stat='count') +
theme_bw() +
labs(title='Ciclo anual ataques de tiburones', x='Mes', y='Ataques')