Tidypandas

Python R Tidyverse Pandas

Utilizar R y Python en Rstudio es cada vez más simple. En este post, se muestran las similitudes y diferencias entre tidyverse y pandas para la manipulación de datos.

true , true
06-13-2021

Tidyverse 🤍 Pandas

Utilizar R y Python en Rstudio es cada vez más simple 🙌. Mediante reticulate 📦 es posible compartir objetos entre ambos lenguajes, visualizando los mismos en el environment. En este post, se muestra las similitudes y diferencias entre tidyverse 📦y pandas 📦 para manipulación de datos. En ambos casos se visualizan los resultados de con los mismos gráficos de ggplot 📦.

👉 Para trabajar con python en Rstudio, una opción es la siguiente:

reticulate::conda_create(envname='tidypandas', python_version="3.9")

O mediante reticulate:

reticulate::conda_install(envname = 'tidypandas', 
                          packages='numpy', 
                          channel='conda-forge')

reticulate::conda_install(envname = 'tidypandas',
                          packages='pandas', 
                          channel='conda-forge')

Se define que el environment a utilizar es el que ha sido creado:

reticulate::use_condaenv(condaenv = 'tidypandas', required = TRUE)

Librerías 📚

A continuación, se cargan las librerías utilizadas para elaborar este post.

🔹 Las siguientes librerías de R se cargan utilizando un chunk de R:

🔹Se utiliza un chunk python para cargar las librerías de python:

import pandas as pd
import numpy as np

Datos 📊

Para estos ejemplos, se utilizan datos obtenidos de Kaggle: IMDb movies extensive dataset🎥.

df <- read_csv('https://raw.githubusercontent.com/karbartolome/data/main/IMDb_df.csv', 
               locale = readr::locale(encoding = "latin1")) 

Se visualizan las primeras observaciones:

df %>% head(5) %>% gt() %>% 
  tab_header('Datos', subtitle='IMDb movies extensive dataset') %>% 
  opt_align_table_header(align='left')
Datos
IMDb movies extensive dataset
imdb_title_id year title language country votes avg_vote reviews_from_users reviews_from_critics weighted_average_vote total_votes mean_vote median_vote votes_10 votes_9 votes_8 votes_7 votes_6 votes_5 votes_4 votes_3 votes_2 votes_1 allgenders_0age_avg_vote allgenders_0age_votes allgenders_18age_avg_vote allgenders_18age_votes allgenders_30age_avg_vote allgenders_30age_votes allgenders_45age_avg_vote allgenders_45age_votes males_allages_avg_vote males_allages_votes males_0age_avg_vote males_0age_votes males_18age_avg_vote males_18age_votes males_30age_avg_vote males_30age_votes males_45age_avg_vote males_45age_votes females_allages_avg_vote females_allages_votes females_0age_avg_vote females_0age_votes females_18age_avg_vote females_18age_votes females_30age_avg_vote females_30age_votes females_45age_avg_vote females_45age_votes top1000_voters_rating top1000_voters_votes us_voters_rating us_voters_votes non_us_voters_rating non_us_voters_votes
tt0000009 1894 Miss Jerry None USA 154 5.9 1 2 5.9 154 5.9 6 12 4 10 43 28 28 9 1 5 14 7.2 4 6.0 38 5.7 50 6.6 35 6.2 97 7 1 5.9 24 5.6 36 6.7 31 6.0 35 7.3 3 5.9 14 5.7 13 4.5 4 5.7 34 6.4 51 6.0 70
tt0000574 1906 The Story of the Kelly Gang None Australia 589 6.1 7 7 6.1 589 6.3 6 57 18 58 137 139 103 28 20 13 16 6.0 1 6.1 114 6.0 239 6.3 115 6.1 425 6 1 6.2 102 6.0 210 6.2 100 6.2 50 NA NA 5.9 12 6.2 23 6.6 14 6.4 66 6.0 96 6.2 331
tt0001892 1911 Den sorte drøm NA Germany, Denmark 188 5.8 5 2 5.8 188 6.0 6 6 6 17 44 52 32 16 5 6 4 NA NA 5.5 25 5.8 72 6.2 62 5.9 146 NA NA 5.5 21 5.9 67 6.2 55 5.7 15 NA NA 5.8 4 5.8 4 6.8 7 5.4 32 6.2 31 5.9 123
tt0002101 1912 Cleopatra English USA 446 5.2 25 3 5.2 446 5.3 5 15 8 16 62 98 117 63 26 25 16 NA NA 5.3 23 5.0 111 5.3 193 5.1 299 NA NA 5.2 20 4.9 96 5.2 171 5.9 39 NA NA 5.7 3 5.5 14 6.1 21 4.9 57 5.5 207 4.7 105
tt0002130 1911 L'Inferno Italian Italy 2237 7.0 31 14 7.0 2237 6.9 7 210 225 436 641 344 169 66 39 20 87 7.5 4 7.0 402 7.0 895 7.1 482 7.0 1607 8 2 7.0 346 7.0 804 7.0 396 7.2 215 7.0 2 7.0 52 7.3 82 7.4 77 6.9 139 7.0 488 7.0 1166

Caso 1: Cantidad de películas por año 📅

Se presenta la misma manipulación de datos con Tidyverse y Pandas para generar un dataframe de cantidad de películas por año.

Tidyverse %>%

peliculas_por_año <- df %>% 
  filter(!is.na(year)) %>% 
  group_by(year) %>% 
  summarise(number_of_films=n())
Cantidad de películas por año
year number_of_films
1894 1
1906 1
1911 5
Show code
peliculas_por_año %>% 
  ggplot(aes(x=year,y=number_of_films))+
  geom_line(color='blue')+
  theme_minimal()+
  labs(x='Año',
       y='Cantidad de películas',
       title='Cantidad de películas por año')

Pandas 🐼

peliculas_por_anio = (r.df
  .query('~year.isna()', engine='python')
  .groupby('year', as_index=False)
  .size()
  .rename({'size':'number_of_films'}, axis=1)
) 
Cantidad de películas por año
year number_of_films
1894 1
1906 1
1911 5
Show code
py$peliculas_por_anio %>% 
  ggplot(aes(x=year,y=number_of_films))+
  geom_line(color='blue')+
  theme_minimal()+
  labs(x='Año',y='Cantidad de películas',
       title='Cantidad de películas por año')

Caso 2: Cantidad y promedio de votos por país por año 📈

👉 Se seleccionan los 8 países con más películas:

paises_frecuentes <- df %>% 
  count(country) %>% 
  arrange(desc(n)) %>% 
  head(8) %>% 
  pull(country)

USA, India, UK, Japan, France, Italy, Canada and Germany

Los pasos a realizar son los siguientes:

Tidyverse %>%

peliculas_paises <- df %>% 
  filter(country %in% paises_frecuentes) %>% 
  group_by(country,year) %>% 
  summarise(avg_vote = mean(avg_vote),
            number = n()) 
Cantidad y promedio de votos por país y año
country year avg_vote number
Canada 1919 6.3 1
Canada 1947 6.3 1
Canada 1952 3.7 1
Show code
p1 <- peliculas_paises %>% 
  ggplot(aes(x = year, y = number)) +
  geom_line(color='blue') +
  scale_x_continuous()+
  facet_wrap(~ country, nrow = 2)+
  theme_minimal()+
  labs(x='Año', y='Cantidad de películas',
       title='Cantidad de películas por país y año')

p2 <- peliculas_paises %>% 
  ggplot(aes(x = year, y = avg_vote)) +
  geom_line(color='red') +
  scale_x_continuous()+
  facet_wrap(~ country, nrow = 2)+
  theme_minimal()+
  labs(x='Año', y='Promedio de votos',
       title='Promedio de votos por país y año')

p1 / p2

Pandas 🐼

paises_frecuentes = r.paises_frecuentes
peliculas_paises = (r.df
  .query("country.isin(@paises_frecuentes)",engine='python')
  .groupby(['country','year'],as_index=False)
  .agg(avg_vote=('avg_vote', 'mean'), 
       number=('imdb_title_id', 'count'))
)
Cantidad y promedio de votos por país y año
country year avg_vote number
Canada 1919 6.3 1
Canada 1947 6.3 1
Canada 1952 3.7 1
Show code
p1 <- py$peliculas_paises %>% 
  ggplot(aes(x = year, y = number)) +
  geom_line(color='blue') +
  scale_x_continuous()+
  facet_wrap(~ country, nrow = 2)+
  theme_minimal()

p2 <- py$peliculas_paises %>% 
  ggplot(aes(x = year, y = avg_vote)) +
  geom_line(color='red') +
  scale_x_continuous()+
  facet_wrap(~ country, nrow = 2)+
  theme_minimal()

p1 / p2

Caso 3: Votos y género 💃🕺

Tidyverse %>%

genero <- df %>% 
  filter(country %in% paises_frecuentes) %>% 
  select(country, imdb_title_id, contains('allages')) %>% 
  drop_na() %>% 
  mutate(
    preferencia_masculina = ifelse(males_allages_avg_vote>females_allages_avg_vote,
    1,0)) %>% 
  group_by(country) %>% 
  summarise(avg_vote_male = mean(males_allages_avg_vote),
            avg_vote_female = mean(females_allages_avg_vote),
            observaciones = n(),
            preferencia_masculina = sum(preferencia_masculina)) %>% 
  mutate(preferencia_masculina = preferencia_masculina/observaciones*100) %>% 
  select(-observaciones) %>% 
  pivot_longer(-country) 
Género y votos
country name value
Canada avg_vote_female 5.584897
Canada avg_vote_male 5.323709
Canada preferencia_masculina 27.207107
Show code
genero %>% 
  ggplot(aes(x=value, y=country, fill=country, color=name))+
  geom_segment(aes(y=country, yend=country, x=0, xend=value, color=name), size=0.5) +
  geom_point( size=3, fill=alpha("white", 0.3), alpha=0.9, shape=21, stroke=1) +
  scale_fill_viridis_d(option='B')+
  facet_wrap(~name, scales='free', nrow=3)+
  theme_minimal()+
  theme(legend.position='none')+
  labs(x='Valor', y='País',
       title='Promedio de votos femeninos y masculinos. % de preferencia masculina')

Pandas 🐼

genero = (r.df
  .query("country.isin(@paises_frecuentes)",engine='python')
  .filter(items = ['country','imdb_title_id']+r.df.filter(regex='allages').columns.tolist())
  .dropna()
  .assign(
    preferencia_masculina = lambda x: np.where(x['males_allages_avg_vote']>x['females_allages_avg_vote'],1,0)
  )
  .groupby(['country'], as_index=False)
  .agg(avg_votes_male   =('males_allages_avg_vote', 'mean'), 
       avg_votes_female =('females_allages_avg_vote', 'mean'), 
       observaciones=('imdb_title_id', 'count'),
       preferencia_masculina = ('preferencia_masculina','sum')
   )
   .assign(preferencia_masculina = lambda x: x['preferencia_masculina']/x['observaciones']*100)
   .drop(['observaciones'],axis=1)
   .melt(id_vars='country')
   .rename({'variable':'name'},axis=1)
)
Género y votos
country name value
Canada avg_votes_female 5.584897
Canada avg_votes_male 5.323709
Canada preferencia_masculina 27.207107
Show code
py$genero %>% 
  ggplot(aes(x=value, y=country, fill=country, color=name))+
  geom_segment(aes(y=country, yend=country, x=0, xend=value, color=name), size=0.5) +
  geom_point( size=3, fill=alpha("white", 0.3), alpha=0.9, shape=21, stroke=1) +
  scale_fill_viridis_d(option='B')+
  facet_wrap(~name, scales='free', nrow=3)+
  theme_minimal()+
  theme(legend.position='none') +
  labs(x='Valor', y='País',
       title='Promedio de votos femeninos y masculinos. % de preferencia masculina')

Comentarios finales 📝

Para quienes utilizan Tidyverse regularmente para manipulación de datos, Pandas puede parecer complejo al comienzo 🤯, debido a que en la práctica habitual no se suele utilizar un enfoque ordenado 🙌. Que no se utilice este enfoque no significa que esto no sea posible. En este post se muestran algunos ejemplos de como hacerlo, mostrando las similitudes con Tidyverse.

Esperamos que haya sido de utilidad, gracias por leernos 👏👏👏.

Contactos

Karina Bartolome, Linkedin, Twitter, Github, Blogpost.

Rafael Zambrano, Linkedin, Twitter, Github, Blogpost.

Citation

For attribution, please cite this work as

Bartolomé & Zambrano (2021, June 13). Karina Bartolome: Tidypandas. Retrieved from https://karbartolome-blog.netlify.app/posts/tidypandas/

BibTeX citation

@misc{bartolomé2021tidypandas,
  author = {Bartolomé, Karina and Zambrano, Rafael},
  title = {Karina Bartolome: Tidypandas},
  url = {https://karbartolome-blog.netlify.app/posts/tidypandas/},
  year = {2021}
}