22. Introducción a Pandas#

“En el pasado, la censura funcionaba bloqueando el flujo de información. En el siglo XXI, la censura funciona inundando a la gente con información irrelevante. Ya no sabemos a qué prestar atención.”

—Yuval Noah Harari, Homo Deus (2016)

22.1. Objetivos de Aprendizaje#

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

  1. Comprender qué es Pandas y por qué es fundamental para el análisis de datos

  2. Crear y manipular Series (estructuras unidimensionales)

  3. Crear y manipular DataFrames (tablas de datos)

  4. Cargar datos desde archivos CSV

  5. Realizar operaciones básicas de exploración de datos

  6. Aplicar estos conocimientos a datos históricos reales sobre religiones del mundo

22.2. ¿Qué es Pandas?#

Pandas es una biblioteca de Python diseñada para el análisis y manipulación de datos. Su nombre proviene de “Panel Data”, un término de econometría que se refiere a datos multidimensionales.

22.2.1. Analogía Histórica: El Archivo Nacional#

Imagina que eres un historiador trabajando en el Archivo Nacional de Chile. Tienes miles de documentos sobre la población, censos y registros históricos. Sin un sistema de organización, encontrar información sería imposible.

Pandas es como tener un sistema de catalogación digital que te permite:

  • Organizar datos en tablas estructuradas

  • Buscar información específica rápidamente

  • Filtrar registros según criterios

  • Calcular estadísticas automáticamente

22.2.2. Las Dos Estructuras Fundamentales#

Estructura

Descripción

Analogía

Series

Columna única de datos

Una lista de censos de un solo año

DataFrame

Tabla con filas y columnas

Una hoja de cálculo completa

22.3. Importando Pandas#

Por convención, Pandas se importa con el alias pd:

# Importamos las librerías necesarias
import pandas as pd
import numpy as np

# Verificamos la versión
print(f"Versión de Pandas: {pd.__version__}")
Versión de Pandas: 2.0.3

22.4. Series: La Estructura Unidimensional#

Una Serie es como una columna de una tabla: tiene valores y un índice que identifica cada valor.

22.4.1. Crear una Serie desde una Lista#

# Población de Chile en millones (datos históricos aproximados)
poblacion_chile = [6.1, 7.6, 9.5, 11.1, 13.2, 15.4, 17.1]

# Crear una Serie básica
serie_poblacion = pd.Series(poblacion_chile)
print("Serie básica:")
print(serie_poblacion)
Serie básica:
0     6.1
1     7.6
2     9.5
3    11.1
4    13.2
5    15.4
6    17.1
dtype: float64
# Crear una Serie con índices personalizados (años)
años = [1950, 1960, 1970, 1980, 1990, 2000, 2010]

serie_poblacion = pd.Series(poblacion_chile, index=años)
print("Serie con índices de años:")
print(serie_poblacion)
Serie con índices de años:
1950     6.1
1960     7.6
1970     9.5
1980    11.1
1990    13.2
2000    15.4
2010    17.1
dtype: float64
# Acceder a elementos de la Serie
print(f"Población en 1970: {serie_poblacion[1970]} millones")
print(f"Población en 2000: {serie_poblacion[2000]} millones")
Población en 1970: 9.5 millones
Población en 2000: 15.4 millones

22.4.2. Atributos de una Serie#

# Explorar atributos de la Serie
print(f"Valores: {serie_poblacion.values}")
print(f"Índices: {serie_poblacion.index.tolist()}")
print(f"Tamaño: {serie_poblacion.size}")
print(f"Tipo de dato: {serie_poblacion.dtype}")
Valores: [ 6.1  7.6  9.5 11.1 13.2 15.4 17.1]
Índices: [1950, 1960, 1970, 1980, 1990, 2000, 2010]
Tamaño: 7
Tipo de dato: float64

22.4.3. Crear una Serie desde un Diccionario#

# Religiones principales y su porcentaje aproximado en Chile (2010)
religiones_chile = {
    "Católicos": 66.7,
    "Evangélicos": 16.4,
    "Sin religión": 11.5,
    "Otras": 5.4
}

serie_religiones = pd.Series(religiones_chile)
print("Distribución religiosa en Chile (%)")
print(serie_religiones)
Distribución religiosa en Chile (%)
Católicos       66.7
Evangélicos     16.4
Sin religión    11.5
Otras            5.4
dtype: float64

22.5. DataFrame: La Estructura Bidimensional#

Un DataFrame es una tabla de datos con filas y columnas, similar a una hoja de cálculo de Excel o una tabla de base de datos.

22.5.1. Crear un DataFrame desde un Diccionario#

# Datos históricos de batallas de la Independencia de Chile
datos_batallas = {
    "Batalla": ["Rancagua", "Chacabuco", "Cancha Rayada", "Maipú"],
    "Año": [1814, 1817, 1818, 1818],
    "Resultado": ["Derrota", "Victoria", "Derrota", "Victoria"],
    "Comandante": ["O'Higgins", "San Martín", "San Martín", "San Martín"]
}

df_batallas = pd.DataFrame(datos_batallas)
print("Batallas de la Independencia de Chile:")
df_batallas
Batallas de la Independencia de Chile:
Batalla Año Resultado Comandante
0 Rancagua 1814 Derrota O'Higgins
1 Chacabuco 1817 Victoria San Martín
2 Cancha Rayada 1818 Derrota San Martín
3 Maipú 1818 Victoria San Martín

22.6. Cargando Datos desde un Archivo CSV#

En la práctica, los datos suelen venir en archivos. El formato CSV (Comma Separated Values) es uno de los más comunes.

22.6.1. El Dataset de Religiones del Mundo (WRP)#

Vamos a trabajar con el World Religion Project, un conjunto de datos académicos que registra la distribución religiosa mundial desde 1945 hasta 2010.

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/

# Cargar el dataset de religiones mundiales (datos globales agregados)
df = pd.read_csv("WRP_global.csv")

# Ver las primeras filas
print("Primeras 5 filas del dataset:")
df.head()
Primeras 5 filas del dataset:
year chrstprot chrstcat chrstorth chrstang chrstothr chrstgen judorth jdcons judref ... taogenpct jaingenpct confgenpct syncgenpct anmgenpct nonreligpct othrgenpct sumreligpct ptctotal version
0 1945 160,887,585 391,332,035 98,501,171 36,955,033 13,674,466 701,350,290 856,827 1,426,350 1,929,388 ... 0.0001 0.0000 0.0000 0.2666 0.0207 0.0955 0.0061 1.012061 0.718667 1.1
1 1950 133,301,043 401,935,856 106,610,911 38,307,544 16,324,768 696,480,122 2,204,231 1,860,297 2,528,641 ... 0.0004 0.0008 0.0012 0.1945 0.0400 0.0869 0.0055 1.011166 0.802432 1.1
2 1955 189,347,338 474,378,130 111,661,338 38,177,572 22,437,724 836,002,102 2,496,432 1,653,007 2,225,241 ... 0.0005 0.0006 0.0012 0.1521 0.0370 0.1199 0.0084 1.011792 0.834366 1.1
3 1960 220,293,770 541,957,872 118,268,109 41,846,700 44,601,144 966,967,595 2,818,847 1,716,903 2,300,405 ... 0.0005 0.0007 0.0012 0.1113 0.0465 0.1488 0.0065 1.013598 0.873137 1.1
4 1965 234,437,703 614,115,021 125,954,494 45,086,639 55,119,929 1,074,713,786 3,295,632 1,760,345 2,348,076 ... 0.0005 0.0007 0.0014 0.1094 0.0491 0.1446 0.0126 1.006728 0.884908 1.1

5 rows × 77 columns

22.6.2. Entendiendo las Columnas del Dataset#

El dataset contiene información sobre diferentes grupos religiosos:

Código

Significado

chrstgen

Cristianos (total general)

chrstcat

Católicos

chrstprot

Protestantes

islmgen

Musulmanes (total)

budgen

Budistas

hindgen

Hindúes

judgen

Judíos

nonrelig

Sin religión

pop

Población mundial total

22.7. Explorando los Datos#

Pandas ofrece varias funciones para explorar rápidamente un dataset.

# Información general del DataFrame
print("Información del dataset:")
print(df.info())
Información del dataset:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 14 entries, 0 to 13
Data columns (total 77 columns):
 #   Column        Non-Null Count  Dtype  
---  ------        --------------  -----  
 0   year          14 non-null     int64  
 1   chrstprot     14 non-null     object 
 2   chrstcat      14 non-null     object 
 3   chrstorth     14 non-null     object 
 4   chrstang      14 non-null     object 
 5   chrstothr     14 non-null     object 
 6   chrstgen      14 non-null     object 
 7   judorth       14 non-null     object 
 8   jdcons        14 non-null     object 
 9   judref        14 non-null     object 
 10  judothr       14 non-null     object 
 11  judgen        14 non-null     object 
 12  islmsun       14 non-null     object 
 13  islmshi       14 non-null     object 
 14  islmibd       14 non-null     object 
 15  islmnat       14 non-null     int64  
 16  islmalw       14 non-null     object 
 17  islmahm       14 non-null     object 
 18  islmothr      14 non-null     object 
 19  islmgen       14 non-null     object 
 20  budmah        14 non-null     object 
 21  budthr        14 non-null     object 
 22  budothr       14 non-null     object 
 23  budgen        14 non-null     object 
 24  zorogen       14 non-null     object 
 25  hindgen       14 non-null     object 
 26  sikhgen       14 non-null     object 
 27  shntgen       14 non-null     object 
 28  bahgen        14 non-null     object 
 29  taogen        14 non-null     object 
 30  jaingen       14 non-null     object 
 31  confgen       14 non-null     object 
 32  syncgen       14 non-null     object 
 33  anmgen        14 non-null     object 
 34  nonrelig      14 non-null     object 
 35  othrgen       14 non-null     object 
 36  sumrelig      14 non-null     object 
 37  pop           14 non-null     object 
 38  worldpop      14 non-null     object 
 39  chrstprotpct  14 non-null     float64
 40  chrstcatpct   14 non-null     float64
 41  chrstorthpct  14 non-null     float64
 42  chrstangpct   14 non-null     float64
 43  chrstothrpct  14 non-null     float64
 44  chrstgenpct   14 non-null     float64
 45  judorthpct    14 non-null     float64
 46  judconspct    14 non-null     float64
 47  judrefpct     14 non-null     float64
 48  judothrpct    14 non-null     float64
 49  judgenpct     14 non-null     float64
 50  islmsunpct    14 non-null     float64
 51  islmshipct    14 non-null     float64
 52  islmibdpct    14 non-null     float64
 53  islmnatpct    14 non-null     int64  
 54  islmalwpct    14 non-null     float64
 55  islmahmpct    14 non-null     float64
 56  islmothrpct   14 non-null     float64
 57  islmgenpct    14 non-null     float64
 58  budmahpct     14 non-null     float64
 59  budthrpct     14 non-null     float64
 60  budothrpct    14 non-null     float64
 61  budgenpct     14 non-null     float64
 62  zorogenpct    14 non-null     float64
 63  hindgenpct    14 non-null     float64
 64  sikhgenpct    14 non-null     float64
 65  shntgenpct    14 non-null     float64
 66  bahgenpct     14 non-null     float64
 67  taogenpct     14 non-null     float64
 68  jaingenpct    14 non-null     float64
 69  confgenpct    14 non-null     float64
 70  syncgenpct    14 non-null     float64
 71  anmgenpct     14 non-null     float64
 72  nonreligpct   14 non-null     float64
 73  othrgenpct    14 non-null     float64
 74  sumreligpct   14 non-null     float64
 75  ptctotal      14 non-null     float64
 76  version       14 non-null     float64
dtypes: float64(37), int64(3), object(37)
memory usage: 8.5+ KB
None
# Dimensiones del DataFrame
print(f"Filas: {df.shape[0]}")
print(f"Columnas: {df.shape[1]}")
Filas: 14
Columnas: 77
# Ver las últimas filas
print("Últimas 3 filas:")
df.tail(3)
Últimas 3 filas:
year chrstprot chrstcat chrstorth chrstang chrstothr chrstgen judorth jdcons judref ... taogenpct jaingenpct confgenpct syncgenpct anmgenpct nonreligpct othrgenpct sumreligpct ptctotal version
11 2000 443,610,594 978,633,933 264,356,291 68,291,261 136,031,105 1,890,923,184 5,433,428 1,849,587 2,306,902 ... 0.0016 0.0007 0.0006 0.0925 0.0274 0.1286 0.0055 1.009635 0.999342 1.1
12 2005 490,942,837 1,013,883,916 255,124,896 69,037,503 161,742,315 1,990,731,467 6,271,171 1,778,851 2,342,301 ... 0.0019 0.0007 0.0006 0.0885 0.0266 0.1156 0.0061 1.008920 0.995949 1.1
13 2010 557,830,085 1,049,709,823 268,783,851 71,770,419 163,818,774 2,111,912,953 6,637,406 1,899,567 2,509,660 ... 0.0014 0.0007 0.0006 0.0865 0.0255 0.1154 0.0039 1.008162 0.999792 1.1

3 rows × 77 columns

# Lista de todas las columnas
print("Columnas disponibles:")
print(df.columns.tolist()[:20])  # Primeras 20 columnas
Columnas disponibles:
['year', 'chrstprot', 'chrstcat', 'chrstorth', 'chrstang', 'chrstothr', 'chrstgen', 'judorth', 'jdcons', 'judref', 'judothr', 'judgen', 'islmsun', 'islmshi', 'islmibd', 'islmnat', 'islmalw', 'islmahm', 'islmothr', 'islmgen']

22.8. Seleccionando Columnas#

Podemos seleccionar columnas específicas para trabajar con datos más manejables.

# Seleccionar una sola columna (retorna una Serie)
años = df["year"]
print("Años en el dataset:")
print(años.values)
Años en el dataset:
[1945 1950 1955 1960 1965 1970 1975 1980 1985 1990 1995 2000 2005 2010]
# Seleccionar múltiples columnas (retorna un DataFrame)
# Usamos las columnas de porcentajes para mejor visualización
columnas_interes = ["year", "chrstgenpct", "islmgenpct", "budgenpct", "hindgenpct", "nonreligpct"]

df_seleccion = df[columnas_interes]
print("Porcentajes de las principales religiones por año:")
df_seleccion
Porcentajes de las principales religiones por año:
year chrstgenpct islmgenpct budgenpct hindgenpct nonreligpct
0 1945 0.4362 0.0813 0.0723 0.0034 0.0955
1 1950 0.3136 0.1325 0.0716 0.1357 0.0869
2 1955 0.3297 0.1357 0.0707 0.1296 0.1199
3 1960 0.3311 0.1490 0.0686 0.1237 0.1488
4 1965 0.3277 0.1569 0.0648 0.1213 0.1446
5 1970 0.3219 0.1620 0.0626 0.1231 0.1611
6 1975 0.3232 0.1650 0.0647 0.1215 0.1716
7 1980 0.3264 0.1517 0.0700 0.1260 0.1730
8 1985 0.3227 0.1600 0.0723 0.1289 0.1688
9 1990 0.3191 0.1882 0.0706 0.1242 0.1578
10 1995 0.3149 0.2037 0.0727 0.1272 0.1415
11 2000 0.3107 0.2159 0.0638 0.1439 0.1286
12 2005 0.3093 0.2216 0.0737 0.1472 0.1156
13 2010 0.3092 0.2277 0.0710 0.1489 0.1154

22.8.1. Renombrando Columnas para Mayor Claridad#

# Crear una copia y renombrar columnas
df_religiones = df_seleccion.copy()

df_religiones = df_religiones.rename(columns={
    "year": "Año",
    "chrstgenpct": "Cristianos %",
    "islmgenpct": "Musulmanes %",
    "budgenpct": "Budistas %",
    "hindgenpct": "Hindúes %",
    "nonreligpct": "Sin Religión %"
})

print("Dataset con nombres más claros:")
df_religiones
Dataset con nombres más claros:
Año Cristianos % Musulmanes % Budistas % Hindúes % Sin Religión %
0 1945 0.4362 0.0813 0.0723 0.0034 0.0955
1 1950 0.3136 0.1325 0.0716 0.1357 0.0869
2 1955 0.3297 0.1357 0.0707 0.1296 0.1199
3 1960 0.3311 0.1490 0.0686 0.1237 0.1488
4 1965 0.3277 0.1569 0.0648 0.1213 0.1446
5 1970 0.3219 0.1620 0.0626 0.1231 0.1611
6 1975 0.3232 0.1650 0.0647 0.1215 0.1716
7 1980 0.3264 0.1517 0.0700 0.1260 0.1730
8 1985 0.3227 0.1600 0.0723 0.1289 0.1688
9 1990 0.3191 0.1882 0.0706 0.1242 0.1578
10 1995 0.3149 0.2037 0.0727 0.1272 0.1415
11 2000 0.3107 0.2159 0.0638 0.1439 0.1286
12 2005 0.3093 0.2216 0.0737 0.1472 0.1156
13 2010 0.3092 0.2277 0.0710 0.1489 0.1154

22.9. Estadísticas Descriptivas#

Pandas facilita calcular estadísticas básicas sobre los datos.

# Estadísticas descriptivas
print("Estadísticas del dataset:")
df_religiones.describe()
Estadísticas del dataset:
Año Cristianos % Musulmanes % Budistas % Hindúes % Sin Religión %
count 14.000000 14.000000 14.000000 14.000000 14.000000 14.000000
mean 1977.500000 0.328264 0.167943 0.069243 0.121757 0.137793
std 20.916501 0.031950 0.040281 0.003703 0.035354 0.028074
min 1945.000000 0.309200 0.081300 0.062600 0.003400 0.086900
25% 1961.250000 0.313925 0.149675 0.065750 0.123250 0.116675
50% 1977.500000 0.322300 0.161000 0.070650 0.126600 0.143050
75% 1993.750000 0.327375 0.199825 0.072125 0.134175 0.160275
max 2010.000000 0.436200 0.227700 0.073700 0.148900 0.173000
# Calcular estadísticas específicas
print(f"Porcentaje promedio de cristianos: {df_religiones['Cristianos %'].mean():.2%}")
print(f"Porcentaje máximo de musulmanes: {df_religiones['Musulmanes %'].max():.2%}")
print(f"Porcentaje mínimo sin religión: {df_religiones['Sin Religión %'].min():.2%}")
Porcentaje promedio de cristianos: 32.83%
Porcentaje máximo de musulmanes: 22.77%
Porcentaje mínimo sin religión: 8.69%

22.10. Accediendo a Filas y Celdas Específicas#

Pandas ofrece dos métodos principales para acceder a datos:

  • .loc[]: Acceso por etiquetas (nombres de índice)

  • .iloc[]: Acceso por posición numérica

# Acceder a una fila por posición
print("Primera fila (1945):")
print(df_religiones.iloc[0])
Primera fila (1945):
Año               1945.0000
Cristianos %         0.4362
Musulmanes %         0.0813
Budistas %           0.0723
Hindúes %            0.0034
Sin Religión %       0.0955
Name: 0, dtype: float64
# Acceder a un rango de filas
print("Filas del índice 0 al 2 (primeras 3 décadas):")
df_religiones.iloc[0:3]
Filas del índice 0 al 2 (primeras 3 décadas):
Año Cristianos % Musulmanes % Budistas % Hindúes % Sin Religión %
0 1945 0.4362 0.0813 0.0723 0.0034 0.0955
1 1950 0.3136 0.1325 0.0716 0.1357 0.0869
2 1955 0.3297 0.1357 0.0707 0.1296 0.1199
# Acceder a una celda específica [fila, columna]
print(f"Porcentaje de cristianos en 1945: {df_religiones.iloc[0, 1]:.2%}")
print(f"Porcentaje de musulmanes en 2010: {df_religiones.iloc[-1, 2]:.2%}")
Porcentaje de cristianos en 1945: 43.62%
Porcentaje de musulmanes en 2010: 22.77%

22.11. Valores Únicos y Conteos#

Para explorar datos categóricos, usamos unique() y value_counts().

# Valores únicos en la columna Año
print("Años disponibles:")
print(df_religiones["Año"].unique())
Años disponibles:
[1945 1950 1955 1960 1965 1970 1975 1980 1985 1990 1995 2000 2005 2010]
# Cantidad de valores únicos
print(f"Cantidad de años registrados: {df_religiones['Año'].nunique()}")
Cantidad de años registrados: 14

22.12. Ejercicio Práctico: Análisis de Tendencias Religiosas#

Ejercicio

Usando el dataset de religiones mundiales:

  1. ¿En qué año el porcentaje de personas sin religión fue mayor?

  2. ¿Cuál fue el cambio porcentual de cristianos entre 1945 y 2010?

  3. ¿Cuál fue el promedio de población musulmana en el período?

# Solución Ejercicio 1: Año con mayor porcentaje sin religión
idx_max = df_religiones["Sin Religión %"].idxmax()
año_max_sin_religion = df_religiones.loc[idx_max, "Año"]
porcentaje_max = df_religiones.loc[idx_max, "Sin Religión %"]

print(f"El año con mayor porcentaje sin religión fue {año_max_sin_religion}")
print(f"Con un {porcentaje_max:.2%} de la población mundial")
El año con mayor porcentaje sin religión fue 1980
Con un 17.30% de la población mundial
# Solución Ejercicio 2: Cambio porcentual de cristianos
cristianos_1945 = df_religiones.iloc[0]["Cristianos %"]
cristianos_2010 = df_religiones.iloc[-1]["Cristianos %"]

cambio = cristianos_2010 - cristianos_1945

print(f"Cristianos en 1945: {cristianos_1945:.2%}")
print(f"Cristianos en 2010: {cristianos_2010:.2%}")
print(f"Cambio: {cambio:.2%}")
Cristianos en 1945: 43.62%
Cristianos en 2010: 30.92%
Cambio: -12.70%
# Solución Ejercicio 3: Promedio de musulmanes
promedio_musulmanes = df_religiones["Musulmanes %"].mean()
print(f"Porcentaje promedio de musulmanes (1945-2010): {promedio_musulmanes:.2%}")
Porcentaje promedio de musulmanes (1945-2010): 16.79%

22.13. Resumen del Capítulo#

Concepto

Función/Método

Descripción

Importar Pandas

import pandas as pd

Cargar la biblioteca

Crear Serie

pd.Series(datos, index)

Estructura 1D

Crear DataFrame

pd.DataFrame(diccionario)

Estructura 2D

Leer CSV

pd.read_csv("archivo.csv")

Cargar datos externos

Ver primeras filas

df.head(n)

Primeras n filas

Ver últimas filas

df.tail(n)

Últimas n filas

Información

df.info()

Tipos de datos y memoria

Dimensiones

df.shape

(filas, columnas)

Columnas

df.columns

Lista de columnas

Estadísticas

df.describe()

Resumen estadístico

Seleccionar columna

df["columna"]

Retorna Serie

Seleccionar múltiples

df[["col1", "col2"]]

Retorna DataFrame

Acceso por posición

df.iloc[fila, col]

Por índice numérico

Acceso por etiqueta

df.loc[fila, col]

Por nombre

Valores únicos

df["col"].unique()

Lista de valores únicos

Contar únicos

df["col"].nunique()

Cantidad de valores únicos

22.14. Referencias#

[MH13]

Zeev Maoz and Errol A. Henderson. The world religion dataset, 1945-2010: logic, estimates, and trends. International Interactions, 39(3):265–291, 2013. URL: https://correlatesofwar.org/data-sets/world-religion-data/, doi:10.1080/03050629.2013.782306.

[McK22]

Wes McKinney. Python for Data Analysis: Data Wrangling with pandas, NumPy, and Jupyter. O'Reilly Media, Sebastopol, CA, 3rd edition, 2022. ISBN 978-1098104030.

[pandasdteam24]

pandas development team. Pandas documentation. 2024. Biblioteca de Python para análisis de datos. URL: https://pandas.pydata.org/docs/.