23. Pandas Avanzado - Filtrado, Agrupación y Análisis#

“Los algoritmos que actualmente controlan el mundo se pueden dividir en dos grandes familias. Unos procesan datos para entender el mundo; otros procesan datos para tomar decisiones. Los más poderosos hacen ambas cosas.”

—Yuval Noah Harari, Homo Deus (2016)

23.1. Objetivos de Aprendizaje#

Al finalizar este capítulo, serás capaz de:

  1. Filtrar datos usando condiciones simples y múltiples

  2. Ordenar DataFrames por una o más columnas

  3. Agrupar datos con groupby() para calcular estadísticas

  4. Crear tablas dinámicas con pivot_table()

  5. Manejar valores faltantes (NaN)

  6. Analizar datos religiosos por país y región

23.2. Preparación del Entorno#

En este capítulo trabajaremos con el dataset WRP National, que contiene datos de religiones por país desde 1945 hasta 2010.

import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

# Configuración para mostrar más columnas
pd.set_option('display.max_columns', 15)
pd.set_option('display.width', None)
# Cargar el dataset de religiones por país
df = pd.read_csv("WRP_national.csv")

# Exploración inicial
print(f"Dimensiones: {df.shape[0]} filas x {df.shape[1]} columnas")
print(f"Países únicos: {df['name'].nunique()}")
print(f"Período: {df['year'].min()} - {df['year'].max()}")
Dimensiones: 1995 filas x 84 columnas
Países únicos: 200
Período: 1945 - 2010
# Ver las primeras filas
df.head()
year state name chrstprot chrstcat chrstorth chrstang ... dualrelig datatype sourcereliab recreliab reliabilevel Version sourcecode
0 1945 2 USA 66069671 38716742 1121898 2400000 ... 0 34 2 10 Medium 1.1 13
1 1950 2 USA 73090083 42635882 3045420 3045420 ... 0 34 6 28 Low 1.1 18
2 1955 2 USA 79294628 46402368 3454916 2572767 ... 0 134 5 10 Medium 1.1 15
3 1960 2 USA 90692928 50587880 3334535 2710065 ... 0 134 2 10 Medium 1.1 13
4 1965 2 USA 94165803 64761783 4792868 2822149 ... 0 134 8 28 Low 1.1 20

5 rows × 84 columns

23.2.1. Entendiendo las Columnas del Dataset#

Fuente de los Datos

Zeev Maoz y Errol A. Henderson (2013). “The World Religion Dataset, 1945-2010: Logic, Estimates, and Trends”. International Interactions, 39: 265-291.

Disponible en: https://correlatesofwar.org/data-sets/world-religion-data/

Columna

Descripción

year

Año del registro

state

Código numérico del país (COW)

name

Código de 3 letras del país

pop

Población total

chrstgenpct

% de cristianos

islmgenpct

% de musulmanes

budgenpct

% de budistas

hindgenpct

% de hindúes

nonreligpct

% sin religión

23.3. Filtrado de Datos#

23.3.1. Analogía Histórica: El Censo Colonial#

Imagina que eres un funcionario colonial en el siglo XVIII y necesitas encontrar información específica en los registros del censo. Por ejemplo, quieres saber cuántas personas vivían en pueblos con más de 1,000 habitantes.

El filtrado en Pandas funciona de manera similar: te permite seleccionar solo las filas que cumplen ciertas condiciones.

23.3.2. Filtrado Simple: Una Condición#

# Filtrar datos de Chile (código: CHL)
chile = df[df['name'] == 'CHL']

print(f"Registros de Chile: {len(chile)}")
chile[['year', 'name', 'pop', 'chrstgenpct', 'nonreligpct']]
Registros de Chile: 14
year name pop chrstgenpct nonreligpct
371 1945 CHL 5540000 0.9196 0.0000
372 1950 CHL 6091000 0.9118 0.0000
373 1955 CHL 6743000 0.9436 0.0000
374 1960 CHL 7614000 0.9822 0.0000
375 1965 CHL 8579000 0.9373 0.0000
376 1970 CHL 9504000 0.9395 0.0338
377 1975 CHL 10350000 0.9325 0.0338
378 1980 CHL 11145000 0.9396 0.0383
379 1985 CHL 12122000 0.9367 0.0000
380 1990 CHL 13100000 0.9430 0.0334
381 1995 CHL 14210000 0.9560 0.0000
382 2000 CHL 15211000 0.9334 0.0300
383 2005 CHL 16267000 0.8775 0.0830
384 2010 CHL 17077416 0.9128 0.0600
# Filtrar datos del año 2010
datos_2010 = df[df['year'] == 2010]

print(f"Países en 2010: {len(datos_2010)}")
datos_2010[['name', 'pop', 'chrstgenpct', 'islmgenpct']].head(10)
Países en 2010: 194
name pop chrstgenpct islmgenpct
13 USA 312750000 0.7454 0.0090
27 CAN 34500000 0.7661 0.0194
35 BHM 313312 0.9660 0.0000
49 CUB 11241161 0.6589 0.0007
63 HAI 9760832 0.8200 0.0002
77 DOM 9956648 0.8700 0.0000
88 JAM 2868630 0.6881 0.0005
99 TRI 1305000 0.5588 0.0503
108 BAR 288705 0.6434 0.0080
115 DMA 73000 0.9200 0.0000
# Filtrar países con más del 90% de cristianos en 2010
muy_cristianos = df[(df['year'] == 2010) & (df['chrstgenpct'] > 0.90)]

print(f"Países con más del 90% de cristianos en 2010: {len(muy_cristianos)}")
muy_cristianos[['name', 'chrstgenpct', 'pop']].sort_values('chrstgenpct', ascending=False).head(10)
Países con más del 90% de cristianos en 2010: 40
name chrstgenpct pop
1905 ETM 0.9800 1127779
255 PAN 0.9793 4255000
759 ROM 0.9751 21433182
656 MLT 0.9727 412911
269 COL 0.9701 46295000
1975 NAU 0.9699 9937
1960 TUV 0.9699 10067
164 MEX 0.9686 112336538
35 BHM 0.9660 313312
1957 KIR 0.9600 103000

23.3.3. Filtrado Múltiple: Combinando Condiciones#

Usamos operadores lógicos:

  • & → AND (ambas condiciones deben cumplirse)

  • | → OR (al menos una condición debe cumplirse)

  • ~ → NOT (negación)

# Países de América Latina en 2010
paises_latam = ['MEX', 'ARG', 'BRA', 'CHL', 'COL', 'PER', 'VEN', 'ECU', 'BOL', 'URU', 'PAR']

latam_2010 = df[(df['year'] == 2010) & (df['name'].isin(paises_latam))]

print("América Latina en 2010:")
latam_2010[['name', 'pop', 'chrstgenpct', 'chrstcatpct', 'chrstprotpct']].sort_values('pop', ascending=False)
América Latina en 2010:
name pop chrstgenpct chrstcatpct chrstprotpct
342 BRA 190755800 0.8823 0.5960 0.2680
164 MEX 112336538 0.9686 0.8272 0.1414
269 COL 46295000 0.9701 0.8200 0.1500
398 ARG 40399992 0.8515 0.7500 0.1000
328 PER 29402646 0.9380 0.8130 0.1250
283 VEN 28834000 0.9500 0.8000 0.1500
384 CHL 17077416 0.9128 0.7586 0.1061
314 ECU 14209151 0.9030 0.8700 0.0200
356 BOL 10312315 0.9426 0.8088 0.1332
370 PAR 6455292 0.9510 0.8800 0.0400
412 URU 3356679 0.8183 0.4710 0.1100
# Países con mayoría musulmana (>50%) Y población mayor a 50 millones en 2010
grandes_musulmanes = df[(df['year'] == 2010) & 
                        (df['islmgenpct'] > 0.50) & 
                        (df['pop'] > 50000000)]

print("Grandes países de mayoría musulmana en 2010:")
grandes_musulmanes[['name', 'pop', 'islmgenpct']].sort_values('pop', ascending=False)
Grandes países de mayoría musulmana en 2010:
name pop islmgenpct
1903 INS 239960000 0.8399
1740 PAK 168446576 0.9569
1074 NIG 154110416 0.5080
1748 BNG 147290688 0.8970
1456 EGY 81277560 0.8643
1428 TUR 76787632 0.9858
1414 IRN 74073560 0.9900

23.4. Ordenando Datos#

El método sort_values() permite ordenar un DataFrame por una o más columnas.

# Ordenar países por población en 2010 (descendente)
top_poblacion = df[df['year'] == 2010].sort_values('pop', ascending=False)

print("Los 10 países más poblados en 2010:")
top_poblacion[['name', 'pop']].head(10)
Los 10 países más poblados en 2010:
name pop
1639 CHN 1345174272
1719 IND 1195000000
13 USA 312750000
1903 INS 239960000
342 BRA 190755800
1740 PAK 168446576
1074 NIG 154110416
1748 BNG 147290688
773 RUS 142400000
1706 JPN 127451704
# Ordenar por múltiples columnas
# Primero por año, luego por porcentaje de cristianos
ordenado = chile.sort_values(['year', 'chrstgenpct'], ascending=[True, False])
ordenado[['year', 'chrstgenpct', 'nonreligpct']]
year chrstgenpct nonreligpct
371 1945 0.9196 0.0000
372 1950 0.9118 0.0000
373 1955 0.9436 0.0000
374 1960 0.9822 0.0000
375 1965 0.9373 0.0000
376 1970 0.9395 0.0338
377 1975 0.9325 0.0338
378 1980 0.9396 0.0383
379 1985 0.9367 0.0000
380 1990 0.9430 0.0334
381 1995 0.9560 0.0000
382 2000 0.9334 0.0300
383 2005 0.8775 0.0830
384 2010 0.9128 0.0600

23.5. Agrupación con groupby()#

23.5.1. Analogía Histórica: El Catastro#

En el Chile colonial, el catastro agrupaba propiedades por tipo (haciendas, chacras, solares) para calcular impuestos. De manera similar, groupby() agrupa filas por valores comunes y permite calcular estadísticas para cada grupo.

Proceso de groupby()

  1. Dividir: Separar los datos en grupos según una columna

  2. Aplicar: Calcular una función (suma, promedio, etc.) para cada grupo

  3. Combinar: Unir los resultados en una nueva estructura

# Promedio de porcentaje cristiano por año (nivel mundial)
cristianos_por_año = df.groupby('year')['chrstgenpct'].mean()

print("Porcentaje promedio de cristianos por año:")
cristianos_por_año
Porcentaje promedio de cristianos por año:
year
1945    0.701698
1950    0.608386
1955    0.582484
1960    0.519113
1965    0.506852
1970    0.498916
1975    0.510285
1980    0.537344
1985    0.537712
1990    0.537367
1995    0.551703
2000    0.550735
2005    0.553080
2010    0.550301
Name: chrstgenpct, dtype: float64
# Múltiples estadísticas a la vez
stats_religiones = df.groupby('year').agg({
    'chrstgenpct': 'mean',
    'islmgenpct': 'mean',
    'nonreligpct': 'mean',
    'pop': 'sum'
}).round(4)

stats_religiones.columns = ['Cristianos %', 'Musulmanes %', 'Sin Religión %', 'Población Total']
print("Estadísticas por año:")
stats_religiones
Estadísticas por año:
Cristianos % Musulmanes % Sin Religión % Población Total
year
1945 0.7017 0.1459 0.0295 1607867655
1950 0.6084 0.1738 0.0323 2220573024
1955 0.5825 0.1779 0.0473 2535824000
1960 0.5191 0.2185 0.0435 2920787124
1965 0.5069 0.2285 0.0420 3279601467
1970 0.4989 0.2483 0.0472 3655452232
1975 0.5103 0.2490 0.0449 4022668800
1980 0.5373 0.2392 0.0438 4265381362
1985 0.5377 0.2403 0.0511 4669065357
1990 0.5374 0.2484 0.0530 5312671680
1995 0.5517 0.2449 0.0637 5669141533
2000 0.5507 0.2415 0.0638 6085782447
2005 0.5531 0.2434 0.0681 6435695175
2010 0.5503 0.2474 0.0734 6830615581
# Evolución religiosa en América Latina
latam = df[df['name'].isin(paises_latam)]

evolucion_latam = latam.groupby('year').agg({
    'chrstcatpct': 'mean',  # Católicos
    'chrstprotpct': 'mean',  # Protestantes
    'nonreligpct': 'mean'   # Sin religión
}).round(4)

evolucion_latam.columns = ['Católicos %', 'Protestantes %', 'Sin Religión %']
print("Evolución religiosa en América Latina:")
evolucion_latam
Evolución religiosa en América Latina:
Católicos % Protestantes % Sin Religión %
year
1945 0.8868 0.0143 0.0003
1950 0.8973 0.0145 0.0013
1955 0.9072 0.0122 0.0004
1960 0.9234 0.0131 0.0005
1965 0.9149 0.0136 0.0002
1970 0.9190 0.0195 0.0094
1975 0.9118 0.0218 0.0151
1980 0.9135 0.0248 0.0167
1985 0.9033 0.0264 0.0225
1990 0.8870 0.0357 0.0238
1995 0.8898 0.0438 0.0072
2000 0.8047 0.0924 0.0513
2005 0.7734 0.1036 0.0682
2010 0.7631 0.1222 0.0653

23.6. Tablas Dinámicas con pivot_table()#

Las tablas dinámicas permiten reorganizar datos para analizar relaciones entre variables.

# Tabla dinámica: Porcentaje de cristianos por país y año (América Latina)
pivot_latam = pd.pivot_table(
    data=latam,
    index='name',      # Filas: países
    columns='year',    # Columnas: años
    values='chrstgenpct',  # Valores: % cristianos
    aggfunc='mean'     # Función de agregación
)

print("Porcentaje de cristianos en América Latina por año:")
(pivot_latam * 100).round(1)  # Convertir a porcentaje
Porcentaje de cristianos en América Latina por año:
year 1945 1950 1955 1960 1965 1970 1975 1980 1985 1990 1995 2000 2005 2010
name
ARG 98.4 94.4 91.0 95.2 96.1 96.9 96.0 94.0 92.5 90.6 92.0 89.9 84.5 85.2
BOL 89.8 96.0 99.8 94.5 94.5 95.0 98.2 98.8 97.2 95.4 98.5 94.2 95.8 94.3
BRA 99.2 95.0 91.6 97.7 95.6 98.6 98.8 98.7 97.8 95.4 97.2 86.1 85.8 88.2
CHL 92.0 91.2 94.4 98.2 93.7 94.0 93.2 94.0 93.7 94.3 95.6 93.3 87.8 91.3
COL 90.5 92.5 96.4 97.4 97.5 98.4 96.9 96.4 96.0 95.9 96.2 94.5 97.0 97.0
ECU 90.0 90.0 92.0 92.5 94.4 98.3 93.6 96.2 95.9 96.2 96.2 90.8 91.6 90.3
MEX 94.0 93.7 93.7 94.4 95.7 94.7 93.4 96.7 97.4 96.9 97.2 96.9 93.6 96.9
PAR 89.7 91.9 91.3 94.4 93.2 95.0 97.2 97.4 95.2 98.2 95.8 97.0 91.0 95.1
PER 93.0 96.2 93.0 96.3 96.1 98.6 96.0 98.4 97.5 99.0 99.0 91.7 96.0 93.8
URU 67.9 72.0 75.4 78.3 73.8 77.4 74.9 70.9 69.0 73.1 72.6 74.0 81.8 81.8
VEN 86.9 90.0 92.8 91.4 93.2 94.7 94.6 98.3 96.2 96.3 97.7 94.2 94.4 95.0
# Comparar católicos vs protestantes en América Latina (2010)
latam_2010_detalle = latam[latam['year'] == 2010][['name', 'chrstcatpct', 'chrstprotpct', 'pop']]
latam_2010_detalle['Católicos %'] = (latam_2010_detalle['chrstcatpct'] * 100).round(1)
latam_2010_detalle['Protestantes %'] = (latam_2010_detalle['chrstprotpct'] * 100).round(1)
latam_2010_detalle['Población (millones)'] = (latam_2010_detalle['pop'] / 1000000).round(1)

print("Católicos vs Protestantes en América Latina (2010):")
latam_2010_detalle[['name', 'Católicos %', 'Protestantes %', 'Población (millones)']].sort_values('Población (millones)', ascending=False)
Católicos vs Protestantes en América Latina (2010):
name Católicos % Protestantes % Población (millones)
342 BRA 59.6 26.8 190.8
164 MEX 82.7 14.1 112.3
269 COL 82.0 15.0 46.3
398 ARG 75.0 10.0 40.4
328 PER 81.3 12.5 29.4
283 VEN 80.0 15.0 28.8
384 CHL 75.9 10.6 17.1
314 ECU 87.0 2.0 14.2
356 BOL 80.9 13.3 10.3
370 PAR 88.0 4.0 6.5
412 URU 47.1 11.0 3.4

23.7. Manejo de Valores Faltantes#

Los datos reales a menudo tienen valores faltantes, representados como NaN (Not a Number).

# Verificar valores faltantes
print("Valores faltantes por columna:")
df[['year', 'name', 'pop', 'chrstgenpct', 'islmgenpct', 'nonreligpct']].isnull().sum()
Valores faltantes por columna:
year           0
name           0
pop            0
chrstgenpct    0
islmgenpct     0
nonreligpct    0
dtype: int64
# Verificar si hay filas con algún valor faltante en columnas clave
filas_con_nan = df[df['pop'].isnull()]
print(f"Filas con población faltante: {len(filas_con_nan)}")
Filas con población faltante: 0
# Eliminar filas con valores faltantes en una columna específica
df_limpio = df.dropna(subset=['pop'])
print(f"Filas originales: {len(df)}")
print(f"Filas después de limpiar: {len(df_limpio)}")
Filas originales: 1995
Filas después de limpiar: 1995

23.8. Creando Nuevas Columnas#

Podemos crear columnas calculadas a partir de datos existentes.

# Crear una copia para trabajar
df_analisis = df[df['year'] == 2010].copy()

# Crear columna: Población en millones
df_analisis['pop_millones'] = df_analisis['pop'] / 1000000

# Crear columna: Religión predominante
def religion_predominante(row):
    religiones = {
        'Cristiana': row['chrstgenpct'],
        'Musulmana': row['islmgenpct'],
        'Budista': row['budgenpct'],
        'Hindú': row['hindgenpct']
    }
    return max(religiones, key=religiones.get)

df_analisis['religion_predominante'] = df_analisis.apply(religion_predominante, axis=1)

print("Países con su religión predominante (2010):")
df_analisis[['name', 'pop_millones', 'religion_predominante']].head(15)
Países con su religión predominante (2010):
name pop_millones religion_predominante
13 USA 312.750000 Cristiana
27 CAN 34.500000 Cristiana
35 BHM 0.313312 Cristiana
49 CUB 11.241161 Cristiana
63 HAI 9.760832 Cristiana
77 DOM 9.956648 Cristiana
88 JAM 2.868630 Cristiana
99 TRI 1.305000 Cristiana
108 BAR 0.288705 Cristiana
115 DMA 0.073000 Cristiana
123 GRN 0.108419 Cristiana
130 SLU 0.164000 Cristiana
137 SVG 0.104000 Cristiana
144 AAB 0.086900 Cristiana
150 SKN 0.050700 Cristiana
# Contar países por religión predominante
print("Distribución de países por religión predominante (2010):")
df_analisis['religion_predominante'].value_counts()
Distribución de países por religión predominante (2010):
religion_predominante
Cristiana    129
Musulmana     49
Budista       13
Hindú          3
Name: count, dtype: int64

23.9. Ejercicio Práctico: Análisis Comparativo#

Ejercicio

Usando el dataset WRP National:

  1. Compara la evolución del catolicismo en Chile vs México (1945-2010)

  2. Encuentra los 5 países con mayor crecimiento de personas sin religión entre 1945 y 2010

  3. Calcula el promedio de población musulmana por década

# Solución Ejercicio 1: Chile vs México - Catolicismo
chile_mex = df[df['name'].isin(['CHL', 'MEX'])][['year', 'name', 'chrstcatpct']]

pivot_comparacion = pd.pivot_table(
    data=chile_mex,
    index='year',
    columns='name',
    values='chrstcatpct'
)

print("Evolución del catolicismo: Chile vs México")
(pivot_comparacion * 100).round(1)
Evolución del catolicismo: Chile vs México
name CHL MEX
year
1945 88.8 93.0
1950 87.2 92.5
1955 90.8 92.5
1960 95.0 93.2
1965 88.0 94.1
1970 89.6 93.0
1975 88.9 91.6
1980 89.0 94.9
1985 84.8 95.7
1990 83.4 93.4
1995 82.0 93.9
2000 81.4 93.8
2005 70.0 89.7
2010 75.9 82.7
# Solución Ejercicio 2: Mayor crecimiento de "sin religión"
# Obtener datos de 1945 y 2010
sin_religion_1945 = df[df['year'] == 1945][['name', 'nonreligpct']].rename(columns={'nonreligpct': 'sin_rel_1945'})
sin_religion_2010 = df[df['year'] == 2010][['name', 'nonreligpct']].rename(columns={'nonreligpct': 'sin_rel_2010'})

# Unir los datasets
comparacion = pd.merge(sin_religion_1945, sin_religion_2010, on='name')
comparacion['cambio'] = comparacion['sin_rel_2010'] - comparacion['sin_rel_1945']

# Top 5 con mayor crecimiento
print("Top 5 países con mayor aumento de personas sin religión (1945-2010):")
top_cambio = comparacion.nlargest(5, 'cambio')
top_cambio['cambio_pct'] = (top_cambio['cambio'] * 100).round(1)
top_cambio[['name', 'cambio_pct']]
Top 5 países con mayor aumento de personas sin religión (1945-2010):
name cambio_pct
62 NEW 38.1
61 AUL 28.1
56 CHN 27.1
22 UKG 21.8
30 GMY 21.0
# Solución Ejercicio 3: Promedio de musulmanes por década
# Crear columna de década
df_decadas = df.copy()
df_decadas['decada'] = (df_decadas['year'] // 10) * 10

# Calcular promedio por década
musulmanes_decada = df_decadas.groupby('decada')['islmgenpct'].mean()

print("Porcentaje promedio de musulmanes por década:")
(musulmanes_decada * 100).round(2)
Porcentaje promedio de musulmanes por década:
decada
1940    14.59
1950    17.60
1960    22.38
1970    24.87
1980    23.98
1990    24.66
2000    24.24
2010    24.74
Name: islmgenpct, dtype: float64

23.10. Exportando Resultados#

Pandas permite guardar DataFrames en diversos formatos.

# Exportar a CSV
latam_2010_detalle[['name', 'Católicos %', 'Protestantes %', 'Población (millones)']].to_csv(
    'analisis_latam_2010.csv', 
    index=False
)
print("Archivo exportado: analisis_latam_2010.csv")
Archivo exportado: analisis_latam_2010.csv

23.11. Resumen del Capítulo#

Operación

Código

Descripción

Filtrar simple

df[df['col'] == valor]

Una condición

Filtrar múltiple

df[(cond1) & (cond2)]

AND: ambas condiciones

Filtrar OR

df[(cond1) | (cond2)]

OR: al menos una

Filtrar lista

df[df['col'].isin(lista)]

Valores en una lista

Ordenar

df.sort_values('col')

Ascendente por defecto

Ordenar desc.

df.sort_values('col', ascending=False)

Descendente

Agrupar

df.groupby('col')['otra'].mean()

Calcular por grupo

Múltiples stats

df.groupby('col').agg({...})

Varias estadísticas

Tabla dinámica

pd.pivot_table(...)

Reorganizar datos

Valores faltantes

df.isnull().sum()

Contar NaN

Eliminar NaN

df.dropna()

Quitar filas con NaN

Nueva columna

df['nueva'] = cálculo

Columna calculada

Exportar CSV

df.to_csv('archivo.csv')

Guardar resultados

23.12. Referencias#