CURSO

Python para Ciencia de Datos

Ph.D. Antonio Escamilla P.
7 sections to go

Capítulo 3: Pandas - Manipulación y Análisis de Datos en Python

¿Qué es Pandas?

Pandas es una biblioteca de Python especializada en la manipulación y análisis de datos estructurados. Proporciona estructuras de datos flexibles y eficientes, así como herramientas para trabajar con datos relacionales, temporales y etiquetados.

Logo de Pandas
Pandas: Una poderosa herramienta para análisis de datos en Python

¿Por qué Pandas es esencial para la ciencia de datos?

En este capítulo, exploraremos los fundamentos de Pandas y aprenderemos a utilizar sus capacidades para manipular y analizar datos de manera eficiente en Python.

Instalación y Primeros Pasos con Pandas

Para comenzar a trabajar con Pandas, primero necesitamos instalarlo. La forma más común es utilizar pip:

# Instalar Pandas
pip install pandas

Si estás utilizando Anaconda, Pandas ya viene incluido. Importemos la biblioteca y verifiquemos su versión:

# Importar Pandas con el alias convencional
import pandas as pd

# Verificar la versión instalada
print(pd.__version__)
2.0.3
Por convención, Pandas se importa con el alias pd. Esto hace que el código sea más limpio y sigue los estándares de la comunidad de ciencia de datos.

Series y DataFrames: Las Estructuras de Datos de Pandas

Pandas proporciona dos estructuras de datos principales que son fundamentales para el análisis de datos:

Series

Una Series es una estructura de datos unidimensional similar a un array, pero con etiquetas llamadas índices:

# Crear una Series desde una lista
import pandas as pd
import numpy as np

serie = pd.Series([10, 20, 30, 40])
print(serie)

# Series con índices personalizados
serie_etiquetada = pd.Series([10, 20, 30, 40], index=['a', 'b', 'c', 'd'])
print("\nSeries con índices personalizados:")
print(serie_etiquetada)

# Series desde un diccionario
diccionario = {'a': 100, 'b': 200, 'c': 300}
serie_dict = pd.Series(diccionario)
print("\nSeries desde diccionario:")
print(serie_dict)
0    10
1    20
2    30
3    40
dtype: int64

Series con índices personalizados:
a    10
b    20
c    30
d    40
dtype: int64

Series desde diccionario:
a    100
b    200
c    300
dtype: int64

DataFrames

Un DataFrame es una estructura de datos bidimensional, similar a una tabla de base de datos o una hoja de cálculo de Excel. Es la estructura más importante en Pandas y la que más utilizaremos:

# Crear un DataFrame desde un diccionario
datos = {
    'Nombre': ['Juan', 'Ana', 'Carlos', 'María'],
    'Edad': [28, 34, 22, 45],
    'Ciudad': ['Madrid', 'Barcelona', 'Sevilla', 'Valencia']
}

df = pd.DataFrame(datos)
print(df)

# DataFrame con índices personalizados
df = pd.DataFrame(datos, index=['p1', 'p2', 'p3', 'p4'])
print("\nDataFrame con índices personalizados:")
print(df)

# Crear DataFrame desde arrays de NumPy
array = np.random.rand(3, 3)
df_array = pd.DataFrame(array, columns=['A', 'B', 'C'])
print("\nDataFrame desde array NumPy:")
print(df_array)
   Nombre  Edad     Ciudad
0    Juan    28     Madrid
1     Ana    34  Barcelona
2  Carlos    22    Sevilla
3   María    45   Valencia

DataFrame con índices personalizados:
    Nombre  Edad     Ciudad
p1    Juan    28     Madrid
p2     Ana    34  Barcelona
p3  Carlos    22    Sevilla
p4   María    45   Valencia

DataFrame desde array NumPy:
          A         B         C
0  0.439735  0.891726  0.963872
1  0.107345  0.974340  0.413908
2  0.499526  0.333006  0.266909
Los DataFrames son ideales para representar datos tabulares y ofrecen muchas funcionalidades para manipularlos. Puedes pensar en ellos como una colección de Series que comparten un mismo índice.

Lectura y Escritura de Datos

Pandas ofrece métodos simples y eficientes para leer y escribir datos en formatos CSV y Excel. Estas funcionalidades son esenciales para trabajar con datos en el mundo real.

Descargar archivo CSV

Lectura desde CSV

# Leer un archivo CSV
df = pd.read_csv('datos.csv')

# Ejemplo con datos integrados (para demostración)
# from io import StringIO
# csv_data = """
# id,nombre,edad,salario
# 1,Juan,28,35000
# 2,Ana,34,42000
# 3,Carlos,22,28000
# 4,María,45,51000
# """
# df = pd.read_csv(StringIO(csv_data))

print(df)
   id  nombre  edad  salario
0   1    Juan    28    35000
1   2     Ana    34    42000
2   3  Carlos    22    28000
3   4   María    45    51000

Escritura a CSV

# Crear un DataFrame para guardar
data = {
    'nombre': ['Ana', 'Carlos', 'Diana', 'Eduardo'],
    'edad': [28, 34, 29, 42],
    'salario': [45000, 55000, 40000, 60000]
}
df_save = pd.DataFrame(data)

# Guardar a CSV
df_save.to_csv('empleados.csv', index=False)
Contenido del CSV guardado:
nombre,edad,salario
Ana,28,45000
Carlos,34,55000
Diana,29,40000
Eduardo,42,60000

Lectura desde Excel

# Para archivos Excel, necesitamos instalar openpyxl
!pip install openpyxl

# Leer un archivo Excel
df_excel = pd.read_excel('datos.xlsx', sheet_name='Hoja1')

# Simulación de lectura Excel (para demostración)
print("DataFrame que se leería de Excel:")
print(df_excel)  # Usamos el DataFrame anterior como ejemplo
Simulación de lectura de archivo Excel:
DataFrame que se leería de Excel:
    nombre  edad  salario
0      Ana    28    45000
1   Carlos    34    55000
2    Diana    29    40000
3  Eduardo    42    60000

Escritura a Excel

# Escribir un DataFrame a Excel
df_save.to_excel('empleados.xlsx', sheet_name='Empleados', index=False)

# Escribir múltiples hojas a un mismo archivo Excel
"""
with pd.ExcelWriter('empresa.xlsx') as writer:
    df_save.to_excel(writer, sheet_name='Empleados', index=False)
"""

print("El DataFrame se guardaría en un archivo Excel")
El DataFrame se guardaría en un archivo Excel
Para archivos CSV grandes o con formato complejo, Pandas ofrece opciones adicionales como chunksize para leer el archivo en fragmentos, skiprows para saltar filas específicas, y parse_dates para convertir automáticamente columnas a formato fecha/hora. Estas opciones pueden mejorar significativamente el rendimiento y la precisión de la importación de datos.

Descripción general del DataFrame

Antes de realizar cualquier análisis, es importante entender la estructura y características de nuestros datos. Pandas proporciona varios métodos útiles para explorar y comprender nuestros DataFrames:

# Importar bibliotecas necesarias
import pandas as pd
import numpy as np

# Crear un DataFrame de ejemplo
data = {
    'nombre': ['Ana', 'Carlos', 'Diana', 'Eduardo', 'Fernanda'],
    'edad': [28, 34, 29, 42, 36],
    'ciudad': ['Madrid', 'Barcelona', 'Valencia', 'Sevilla', 'Madrid'],
    'puntuacion': [85, 92, 78, 96, 89],
    'activo': [True, True, False, True, True]
}

# Crear el DataFrame
df = pd.DataFrame(data)

# Visualizar el DataFrame completo
print("DataFrame completo:")
print(df)
DataFrame completo:
      nombre  edad     ciudad  puntuacion  activo
0       Ana    28     Madrid          85    True
1    Carlos    34  Barcelona          92    True
2     Diana    29   Valencia          78   False
3   Eduardo    42    Sevilla          96    True
4  Fernanda    36     Madrid          89    True

Número de Filas y Columnas

# Obtener la forma (filas, columnas) del DataFrame
print(f"Forma del DataFrame: {df.shape}")

# Número de filas
print(f"Número de filas: {len(df)}")

# Número de columnas
print(f"Número de columnas: {len(df.columns)}")
Forma del DataFrame: (5, 5)
Número de filas: 5
Número de columnas: 5

Información General de los datos

El método info() proporciona un resumen conciso del DataFrame, incluyendo el tipo de datos de cada columna y la cantidad de valores no nulos.

# Obtener información general del DataFrame
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 5 columns):
 #   Column      Non-Null Count  Dtype 
---  ------      --------------  ----- 
 0   nombre      5 non-null      object
 1   edad        5 non-null      int64 
 2   ciudad      5 non-null      object
 3   puntuacion  5 non-null      int64 
 4   activo      5 non-null      bool  
dtypes: bool(1), int64(2), object(2)
memory usage: 316.0+ bytes
El método info() es especialmente útil para identificar valores faltantes (cuando "Non-Null Count" es menor que el número total de filas) y para verificar los tipos de datos de cada columna.

Resumen de estadística descriptiva General

El método describe() genera estadísticas descriptivas para las columnas numéricas, como la media, desviación estándar, mínimo, máximo, y percentiles.

# Obtener estadísticas descriptivas
df.describe()
             edad  puntuacion
count   5.000000    5.000000
mean   33.800000   88.000000
std     5.630275    7.036011
min    28.000000   78.000000
25%    29.000000   85.000000
50%    34.000000   89.000000
75%    36.000000   92.000000
max    42.000000   96.000000
# Para incluir columnas no numéricas
df.describe(include='all')
        nombre        edad      ciudad puntuacion activo
count        5    5.000000           5   5.000000      5
unique       5         NaN           3        NaN      2
top        Ana         NaN      Madrid        NaN   True
freq         1         NaN           2        NaN      4
mean       NaN   33.800000         NaN  88.000000    NaN
std        NaN    5.630275         NaN   7.036011    NaN
min        NaN   28.000000         NaN  78.000000    NaN
25%        NaN   29.000000         NaN  85.000000    NaN
50%        NaN   34.000000         NaN  89.000000    NaN
75%        NaN   36.000000         NaN  92.000000    NaN
max        NaN   42.000000         NaN  96.000000    NaN
Para las columnas categóricas, describe() con el parámetro include='all' muestra información como el número de valores únicos, el valor más frecuente (top), y cuántas veces aparece (freq).

Ver Primeros elementos del DataFrame

# Ver las primeras 3 filas del DataFrame
df.head(3)
   nombre  edad     ciudad  puntuacion  activo
0     Ana    28     Madrid          85    True
1  Carlos    34  Barcelona          92    True
2   Diana    29   Valencia          78   False

Ver Últimos elementos del DataFrame

# Ver las últimas 2 filas del DataFrame
df.tail(2)
      nombre  edad  ciudad  puntuacion  activo
3   Eduardo    42  Sevilla          96    True
4  Fernanda    36   Madrid          89    True

Ver elementos aleatorios del DataFrame

# Ver filas aleatorias del DataFrame
df.sample(2)
     nombre  edad     ciudad  puntuacion  activo
1    Carlos    34  Barcelona          92    True
4  Fernanda    36     Madrid          89    True
El método sample() es útil para inspeccionar muestras aleatorias de datos, especialmente cuando trabajamos con DataFrames muy grandes. Puede ayudar a identificar patrones o problemas que podrían no ser evidentes al examinar solo las primeras o últimas filas.

Selección e Indexación

Pandas ofrece múltiples formas de seleccionar y acceder a datos dentro de un DataFrame. Estas operaciones son fundamentales para el análisis y la manipulación de datos.

# Seleccionar una columna (devuelve una Series)
print(df['nombre'])

# Seleccionar múltiples columnas (devuelve un DataFrame)
print("\nSeleccionando múltiples columnas:")
print(df[['nombre', 'edad']])
0         Ana
1      Carlos
2       Diana
3     Eduardo
4    Fernanda
Name: nombre, dtype: object

Seleccionando múltiples columnas:
     nombre  edad
0       Ana    28
1    Carlos    34
2     Diana    29
3   Eduardo    42
4  Fernanda    36

Creando una Nueva Columna

# Crear una nueva columna
df['año_nacimiento'] = 2025 - df['edad']

# Crear una columna calculada
df['puntuacion_ajustada'] = df['puntuacion'] * (1 + df['edad'] / 100)

print(df)
      nombre  edad     ciudad  puntuacion  activo  año_nacimiento  puntuacion_ajustada
0       Ana    28     Madrid          85    True            1997             108.80
1    Carlos    34  Barcelona          92    True            1991             123.28
2     Diana    29   Valencia          78   False            1996             100.62
3   Eduardo    42    Sevilla          96    True            1983             136.32
4  Fernanda    36     Madrid          89    True            1989             121.04

Eliminando Columnas

# Eliminar una columna
df_reducido = df.drop('puntuacion_ajustada', axis=1)
print(df_reducido)

# Eliminar múltiples columnas
df_reducido = df.drop(['puntuacion_ajustada', 'año_nacimiento'], axis=1)
print("\nDF con múltiples columnas eliminadas:")
print(df_reducido)
      nombre  edad     ciudad  puntuacion  activo  año_nacimiento
0       Ana    28     Madrid          85    True            1997
1    Carlos    34  Barcelona          92    True            1991
2     Diana    29   Valencia          78   False            1996
3   Eduardo    42    Sevilla          96    True            1983
4  Fernanda    36     Madrid          89    True            1989

DF con múltiples columnas eliminadas:
     nombre  edad     ciudad  puntuacion  activo
0       Ana    28     Madrid          85    True
1    Carlos    34  Barcelona          92    True
2     Diana    29   Valencia          78   False
3   Eduardo    42    Sevilla          96    True
4  Fernanda    36     Madrid          89    True
Por defecto, el método drop() devuelve un nuevo DataFrame sin modificar el original. Si quieres modificar el DataFrame original, utiliza inplace=True: df.drop('columna', axis=1, inplace=True).

Obtener los nombres de las columnas y los índices (index)

# Obtener nombres de columnas
print(f"Nombres de columnas: {df.columns.tolist()}")

# Obtener índices
print(f"Índices: {df.index.tolist()}")
Nombres de columnas: ['nombre', 'edad', 'ciudad', 'puntuacion', 'activo', 'año_nacimiento', 'puntuacion_ajustada']
Índices: [0, 1, 2, 3, 4]

Seleccionando Filas y Columnas

Pandas ofrece varios métodos para seleccionar datos, incluyendo loc (por etiqueta) y iloc (por posición):

# Seleccionar por posición con iloc
# Sintaxis: df.iloc[filas, columnas]
print("Selección por posición (iloc):")
print("Primera fila, todas las columnas:")
print(df.iloc[0, :])

print("\nTercera fila, columnas 1 y 3:")
print(df.iloc[2, [1, 3]])

print("\nPrimeras tres filas, primeras dos columnas:")
print(df.iloc[0:3, 0:2])
Selección por posición (iloc):
Primera fila, todas las columnas:
nombre                      Ana
edad                         28
ciudad                   Madrid
puntuacion                   85
activo                     True
año_nacimiento             1997
puntuacion_ajustada       108.8
Name: 0, dtype: object

Tercera fila, columnas 1 y 3:
edad           29
puntuacion     78
Name: 2, dtype: object

Primeras tres filas, primeras dos columnas:
   nombre  edad
0     Ana    28
1  Carlos    34
2   Diana    29
# Seleccionar por etiqueta con loc
# Sintaxis: df.loc[etiquetas_filas, etiquetas_columnas]
print("Selección por etiqueta (loc):")
print("Fila con índice 1, columnas 'nombre' y 'ciudad':")
print(df.loc[1, ['nombre', 'ciudad']])

print("\nFilas 2 a 4, columnas 'nombre' a 'puntuacion':")
print(df.loc[2:4, 'nombre':'puntuacion'])
Selección por etiqueta (loc):
Fila con índice 1, columnas 'nombre' y 'ciudad':
nombre       Carlos
ciudad    Barcelona
Name: 1, dtype: object

Filas 2 a 4, columnas 'nombre' a 'puntuacion':
     nombre  edad    ciudad  puntuacion
2     Diana    29  Valencia          78
3   Eduardo    42   Sevilla          96
4  Fernanda    36    Madrid          89
La diferencia clave entre loc e iloc es que loc utiliza etiquetas (nombres de filas y columnas) mientras que iloc utiliza índices numéricos basados en posición (0, 1, 2, ...). Además, con loc el último índice es inclusivo, mientras que con iloc es exclusivo.

Seleccionar un subconjunto de filas y columnas

# Diferentes métodos para seleccionar datos
# 1. Usando notación de corchetes
print("Selección de filas por índice:")
print(df[1:3])  # Filas 1 y 2

# 2. Usando métodos especiales
print("\nSelección de filas donde edad > 30:")
print(df[df['edad'] > 30])  # Filas que cumplen la condición
Selección de filas por índice:
    nombre  edad     ciudad  puntuacion  activo  año_nacimiento  puntuacion_ajustada
1   Carlos    34  Barcelona          92    True            1991             123.28
2    Diana    29   Valencia          78   False            1996             100.62

Selección de filas donde edad > 30:
     nombre  edad     ciudad  puntuacion  activo  año_nacimiento  puntuacion_ajustada
1    Carlos    34  Barcelona          92    True            1991             123.28
3   Eduardo    42    Sevilla          96    True            1983             136.32
4  Fernanda    36     Madrid          89    True            1989             121.04
Cuando utilizas la notación de corchetes df[1:3] para seleccionar filas, el comportamiento es similar al slicing de listas en Python (el límite superior es exclusivo). Sin embargo, cuando usas loc, ambos límites son inclusivos: df.loc[1:3] incluye las filas 1, 2 y 3.

Selección Condicional o Filtros

Podemos usar condiciones para filtrar los datos que queremos ver. Estas condiciones generan máscaras booleanas que se aplican al DataFrame:

# Filtrar por una condición
mayores_30 = df['edad'] > 30
print("Máscara booleana (mayores de 30):")
print(mayores_30)

print("\nPersonas mayores de 30 años:")
print(df[mayores_30])

# Filtrar por múltiples condiciones
print("\nPersonas mayores de 30 años que viven en Madrid:")
print(df[(df['edad'] > 30) & (df['ciudad'] == 'Madrid')])

# Personas que viven en Madrid o Barcelona
print("\nPersonas que viven en Madrid o Barcelona:")
print(df[df['ciudad'].isin(['Madrid', 'Barcelona'])])
Máscara booleana (mayores de 30):
0    False
1     True
2    False
3     True
4     True
Name: edad, dtype: bool

Personas mayores de 30 años:
     nombre  edad     ciudad  puntuacion  activo  año_nacimiento  puntuacion_ajustada
1    Carlos    34  Barcelona          92    True            1991             123.28
3   Eduardo    42    Sevilla          96    True            1983             136.32
4  Fernanda    36     Madrid          89    True            1989             121.04

Personas mayores de 30 años que viven en Madrid:
     nombre  edad ciudad  puntuacion  activo  año_nacimiento  puntuacion_ajustada
4  Fernanda    36 Madrid          89    True            1989             121.04

Personas que viven en Madrid o Barcelona:
     nombre  edad     ciudad  puntuacion  activo  año_nacimiento  puntuacion_ajustada
0       Ana    28     Madrid          85    True            1997             108.80
1    Carlos    34  Barcelona          92    True            1991             123.28
4  Fernanda    36     Madrid          89    True            1989             121.04
Usa el operador & para condiciones AND (y) y el operador | para condiciones OR (o). Asegúrate de encerrar cada condición entre paréntesis cuando las combines para evitar problemas de precedencia de operadores.

.query() Búsqueda condicional

El método query() proporciona una forma más legible de filtrar datos mediante una cadena de texto:

# Usar query para filtrar datos
print("Personas menores de 35 años:")
print(df.query('edad < 35'))

print("\nPersonas activas que viven en Madrid:")
print(df.query('ciudad == "Madrid" and activo == True'))

print("\nPersonas con puntuación superior a 90 o menores de 30 años:")
print(df.query('puntuacion > 90 or edad < 30'))
Personas menores de 35 años:
  nombre  edad     ciudad  puntuacion  activo  año_nacimiento  puntuacion_ajustada
0     Ana    28     Madrid          85    True            1997             108.80
2   Diana    29   Valencia          78   False            1996             100.62

Personas activas que viven en Madrid:
     nombre  edad ciudad  puntuacion  activo  año_nacimiento  puntuacion_ajustada
0       Ana    28 Madrid          85    True            1997              108.80
4  Fernanda    36 Madrid          89    True            1989              121.04

Personas con puntuación superior a 90 o menores de 30 años:
     nombre  edad     ciudad  puntuacion  activo  año_nacimiento  puntuacion_ajustada
0       Ana    28     Madrid          85    True            1997             108.80
1    Carlos    34  Barcelona          92    True            1991             123.28
2     Diana    29   Valencia          78   False            1996             100.62
3   Eduardo    42    Sevilla          96    True            1983             136.32
query() ofrece una sintaxis más concisa y legible que utilizar filtros con corchetes, especialmente para condiciones complejas. La expresión se escribe como una cadena que se evalúa en el contexto del DataFrame.

Cambio de columna de Indexación

Podemos establecer una o más columnas como índice del DataFrame, lo que facilita ciertas operaciones de selección y agrupación:

# Establecer 'nombre' como índice
df_por_nombre = df.set_index('nombre')
print("DataFrame indexado por nombre:")
print(df_por_nombre)

# Acceder a una fila por su índice
print("\nDatos de Diana:")
print(df_por_nombre.loc['Diana'])

# Establecer múltiples columnas como índice
df_multi = df.set_index(['ciudad', 'nombre'])
print("\nDataFrame con índice múltiple (ciudad, nombre):")
print(df_multi)

# Acceder a elementos con índice múltiple
print("\nDatos de personas en Madrid:")
print(df_multi.loc['Madrid'])

print("\nDatos de Eduardo en Sevilla:")
print(df_multi.loc[('Sevilla', 'Eduardo')])
DataFrame indexado por nombre:
        edad     ciudad  puntuacion  activo  año_nacimiento  puntuacion_ajustada
nombre                                                                           
Ana       28     Madrid          85    True            1997              108.80
Carlos    34  Barcelona          92    True            1991              123.28
Diana     29   Valencia          78   False            1996              100.62
Eduardo   42    Sevilla          96    True            1983              136.32
Fernanda  36     Madrid          89    True            1989              121.04

Datos de Diana:
edad                       29
ciudad               Valencia
puntuacion                 78
activo                  False
año_nacimiento           1996
puntuacion_ajustada    100.62
Name: Diana, dtype: object

DataFrame con índice múltiple (ciudad, nombre):
                    edad  puntuacion  activo  año_nacimiento  puntuacion_ajustada
ciudad    nombre                                                               
Madrid    Ana         28          85    True            1997              108.80
          Fernanda    36          89    True            1989              121.04
Barcelona Carlos      34          92    True            1991              123.28
Valencia  Diana       29          78   False            1996              100.62
Sevilla   Eduardo     42          96    True            1983              136.32

Datos de personas en Madrid:
          edad  puntuacion  activo  año_nacimiento  puntuacion_ajustada
nombre                                                                   
Ana          28          85    True            1997              108.80
Fernanda     36          89    True            1989              121.04

Datos de Eduardo en Sevilla:
edad                       42
puntuacion                 96
activo                   True
año_nacimiento           1983
puntuacion_ajustada    136.32
Name: (Sevilla, Eduardo), dtype: object
Establecer un índice adecuado puede mejorar significativamente el rendimiento de ciertas operaciones, especialmente para DataFrames grandes. También facilita la selección y el agrupamiento de datos. Si necesitas restablecer el índice original, puedes usar el método reset_index().
# Restablecer el índice
df_reset = df_por_nombre.reset_index()
print("DataFrame con índice restablecido:")
print(df_reset)

# Restablecer índice múltiple
df_reset_multi = df_multi.reset_index()
print("\nDataFrame con índice múltiple restablecido:")
print(df_reset_multi)
DataFrame con índice restablecido:
     nombre  edad     ciudad  puntuacion  activo  año_nacimiento  puntuacion_ajustada
0       Ana    28     Madrid          85    True            1997              108.80
1    Carlos    34  Barcelona          92    True            1991              123.28
2     Diana    29   Valencia          78   False            1996              100.62
3   Eduardo    42    Sevilla          96    True            1983              136.32
4  Fernanda    36     Madrid          89    True            1989              121.04

DataFrame con índice múltiple restablecido:
      ciudad    nombre  edad  puntuacion  activo  año_nacimiento  puntuacion_ajustada
0     Madrid       Ana    28          85    True            1997              108.80
1     Madrid  Fernanda    36          89    True            1989              121.04
2  Barcelona    Carlos    34          92    True            1991              123.28
3   Valencia     Diana    29          78   False            1996              100.62
4    Sevilla   Eduardo    42          96    True            1983              136.32
Cuando restableces un índice múltiple, las columnas utilizadas como índices se añaden al inicio del DataFrame en el mismo orden en que se establecieron como índices.

Datos Categóricos

Los datos categóricos representan valores que pertenecen a un conjunto finito de categorías. Pandas proporciona el tipo de datos Categorical que es especialmente eficiente para representar este tipo de variables.

# Crear un DataFrame con datos categóricos
datos = {
   'nombre': ['Ana', 'Carlos', 'Diana', 'Eduardo', 'Fernanda'],
   'genero': ['F', 'M', 'F', 'M', 'F'],
   'nivel_educativo': ['Universitario', 'Posgrado', 'Secundario', 'Universitario', 'Posgrado'],
   'region': ['Norte', 'Sur', 'Norte', 'Centro', 'Sur']
}

df_cat = pd.DataFrame(datos)
print(df_cat)
      nombre genero nivel_educativo region
0       Ana      F    Universitario  Norte
1    Carlos      M         Posgrado    Sur
2     Diana      F       Secundario  Norte
3   Eduardo      M    Universitario Centro
4  Fernanda      F         Posgrado    Sur
# Convertir columna a tipo categórico
df_cat['genero'] = df_cat['genero'].astype('category')
df_cat['region'] = df_cat['region'].astype('category')

# Verificar los tipos de datos
print(df_cat.dtypes)

# Ver las categorías
print("\nCategorías de género:")
print(df_cat['genero'].cat.categories)

print("\nCategorías de región:")
print(df_cat['region'].cat.categories)
nombre                object
genero              category
nivel_educativo       object
region              category
dtype: object

Categorías de género:
Index(['F', 'M'], dtype='object')

Categorías de región:
Index(['Centro', 'Norte', 'Sur'], dtype='object')
El tipo de datos category consume menos memoria que los tipos object o string, lo que puede ser significativo para conjuntos de datos grandes con valores repetidos.

Categorías Ordinales

Las categorías ordinales son aquellas que tienen un orden natural o jerarquía. Pandas permite definir este orden al crear variables categóricas ordinales.

# Definir categorías ordinales para nivel educativo
niveles_orden = ['Primario', 'Secundario', 'Universitario', 'Posgrado']

# Convertir a categórica ordinal
df_cat['nivel_educativo'] = pd.Categorical(df_cat['nivel_educativo'], 
                                        categories=niveles_orden, 
                                        ordered=True)

print("Tipo de dato de nivel_educativo:")
print(df_cat['nivel_educativo'].dtype)

# Comparaciones con categorías ordinales
print("\nPersonas con nivel educativo mayor que Secundario:")
print(df_cat[df_cat['nivel_educativo'] > 'Secundario'])

# Obtener valores estadísticos
print("\nValor mínimo (menor nivel):", df_cat['nivel_educativo'].min())
print("Valor máximo (mayor nivel):", df_cat['nivel_educativo'].max())
Tipo de dato de nivel_educativo:
category

Personas con nivel educativo mayor que Secundario:
     nombre genero nivel_educativo region
0       Ana      F   Universitario  Norte
1    Carlos      M        Posgrado    Sur
3   Eduardo      M   Universitario Centro
4  Fernanda      F        Posgrado    Sur

Valor mínimo (menor nivel): Secundario
Valor máximo (mayor nivel): Posgrado
Las categorías ordinales permiten operaciones de comparación que no son posibles con categorías nominales (sin orden). Esto es útil para filtrar datos basados en niveles o rangos.

Corrección de tipos de datos (Casting)

A menudo, los datos importados no tienen el tipo de datos correcto. El "casting" o conversión de tipos es esencial para asegurar que Pandas interprete correctamente los datos.

# Crear un DataFrame con datos de diferentes tipos
datos_mixtos = {
   'edad': ['28', '34', '29', '42', '36'],  # Números como strings
   'salario': ['85000.5', '92000.75', '78500.25', '96000.0', '89300.5'],  # Decimales como strings
   'trabaja_remoto': ['True', 'True', 'False', 'True', 'False'],  # Booleanos como strings
   'nivel': ['Junior', 'Senior', 'Junior', 'Senior', 'Mid']  # Categorías como strings
}

df_tipos = pd.DataFrame(datos_mixtos)
print("DataFrame original con tipos incorrectos:")
print(df_tipos.dtypes)
print("\n", df_tipos.head())
DataFrame original con tipos incorrectos:
edad             object
salario          object
trabaja_remoto   object
nivel            object
dtype: object

      edad    salario trabaja_remoto  nivel
0       28    85000.5          True  Junior
1       34   92000.75          True  Senior
2       29   78500.25         False  Junior
3       42    96000.0          True  Senior
4       36    89300.5         False     Mid
# Conversión de tipos numéricos
df_tipos['edad'] = df_tipos['edad'].astype(int)
df_tipos['salario'] = df_tipos['salario'].astype(float)

print("Después de convertir tipos numéricos:")
print(df_tipos.dtypes)
print("\nPrimera fila con valores convertidos:")
print(df_tipos.iloc[0])
Después de convertir tipos numéricos:
edad              int64
salario         float64
trabaja_remoto   object
nivel            object
dtype: object

Primera fila con valores convertidos:
edad                      28
salario              85000.5
trabaja_remoto          True
nivel                 Junior
Name: 0, dtype: object

Casting variables Categóricas nominales

# Convertir a tipo categórico
df_tipos['nivel'] = df_tipos['nivel'].astype('category')

print("Tipo de dato de nivel después de la conversión:")
print(df_tipos['nivel'].dtype)

print("\nCategorías disponibles:")
print(df_tipos['nivel'].cat.categories)
Tipo de dato de nivel después de la conversión:
category

Categorías disponibles:
Index(['Junior', 'Mid', 'Senior'], dtype='object')

Casting variables Categóricas Ordinales

# Definir un orden para los niveles
niveles_jerarquia = ['Junior', 'Mid', 'Senior']

# Convertir a categórica ordinal
df_tipos['nivel'] = pd.Categorical(df_tipos['nivel'], 
                                categories=niveles_jerarquia, 
                                ordered=True)

print("Tipo de dato de nivel como categórica ordinal:")
print(df_tipos['nivel'].dtype)

# Filtrando usando el orden
print("\nEmpleados con nivel Mid o superior:")
print(df_tipos[df_tipos['nivel'] >= 'Mid'])
Tipo de dato de nivel como categórica ordinal:
category

Empleados con nivel Mid o superior:
   edad   salario trabaja_remoto  nivel
1    34  92000.75           True Senior
3    42  96000.00           True Senior
4    36  89300.50          False    Mid

Casting variables booleanas

# Convertir strings 'True'/'False' a booleanos
df_tipos['trabaja_remoto'] = df_tipos['trabaja_remoto'].astype(bool)

print("Tipo de dato después de conversión a booleano:")
print(df_tipos['trabaja_remoto'].dtype)

# Filtrar usando valores booleanos
print("\nEmpleados que trabajan remotamente:")
print(df_tipos[df_tipos['trabaja_remoto']])
Tipo de dato después de conversión a booleano:
bool

Empleados que trabajan remotamente:
   edad    salario trabaja_remoto  nivel
0    28   85000.50           True Junior
1    34   92000.75           True Senior
3    42   96000.00           True Senior
Al convertir strings a booleanos, cualquier string no vacío se convierte a True y los strings vacíos se convierten a False. Para valores como 'True' y 'False', esto funciona como se espera, pero tenga cuidado con otros strings.

Resultado Casting

# Verificar todos los tipos después de la conversión
print("Tipos de datos finales:")
print(df_tipos.dtypes)

# Resumen estadístico ahora con los tipos correctos
print("\nResumen estadístico con tipos corregidos:")
print(df_tipos.describe(include='all'))
Tipos de datos finales:
edad              int64
salario         float64
trabaja_remoto     bool
nivel          category
dtype: object

Resumen estadístico con tipos corregidos:
            edad        salario trabaja_remoto   nivel
count   5.000000       5.000000              5       5
unique       NaN            NaN              2       3
top          NaN            NaN           True  Junior
freq         NaN            NaN              3       2
mean   33.800000   88160.400000            NaN     NaN
std     5.630275    6676.336308            NaN     NaN
min    28.000000   78500.250000            NaN  Junior
25%    29.000000   85000.500000            NaN  Junior
50%    34.000000   89300.500000            NaN     Mid
75%    36.000000   92000.750000            NaN  Senior
max    42.000000   96000.000000            NaN  Senior
La conversión correcta de tipos de datos no solo mejora la semántica de los datos, sino que también optimiza el rendimiento y el uso de memoria. Además, habilita operaciones específicas para cada tipo, como comparaciones para categorías ordinales y operaciones matemáticas para números.
# Verificar el uso de memoria antes y después del casting
df_original = pd.DataFrame(datos_mixtos)  # Recrear el DataFrame original

# Usar info() para ver el uso de memoria
print("Uso de memoria antes de optimizar tipos:")
df_original.info(memory_usage='deep')

print("\nUso de memoria después de optimizar tipos:")
df_tipos.info(memory_usage='deep')
Uso de memoria antes de optimizar tipos:

RangeIndex: 5 entries, 0 to 4
Data columns (total 4 columns):
#   Column          Non-Null Count  Dtype 
---  ------          --------------  ----- 
0   edad            5 non-null      object
1   salario         5 non-null      object
2   trabaja_remoto  5 non-null      object
3   nivel           5 non-null      object
dtypes: object(4)
memory usage: 940.0 bytes

Uso de memoria después de optimizar tipos:

RangeIndex: 5 entries, 0 to 4
Data columns (total 4 columns):
#   Column          Non-Null Count  Dtype   
---  ------          --------------  -----   
0   edad            5 non-null      int64   
1   salario         5 non-null      float64 
2   trabaja_remoto  5 non-null      bool    
3   nivel           5 non-null      category
dtypes: bool(1), category(1), float64(1), int64(1)
memory usage: 606.0 bytes

Datos Faltantes (Missing data)

Los datos faltantes son un problema común en el análisis de datos. Pandas representa los valores faltantes como NaN (Not a Number) para tipos numéricos o None para otros tipos. Estas ausencias de información requieren un tratamiento especial.

# Crear un DataFrame con datos faltantes
datos_incompletos = {
   'nombre': ['Ana', 'Carlos', 'Diana', None, 'Fernanda'],
   'edad': [28, 34, None, 42, 36],
   'ciudad': ['Madrid', None, 'Valencia', 'Sevilla', 'Madrid'],
   'puntuacion': [85, 92, 78, None, 89]
}

df_na = pd.DataFrame(datos_incompletos)
print("DataFrame con valores faltantes:")
print(df_na)
DataFrame con valores faltantes:
     nombre   edad    ciudad  puntuacion
0       Ana  28.00    Madrid        85.0
1    Carlos  34.00      None        92.0
2     Diana    NaN  Valencia        78.0
3      None  42.00   Sevilla         NaN
4  Fernanda  36.00    Madrid        89.0

Detectar si faltan datos

# Verificar si hay valores nulos
print("¿Hay valores nulos en el DataFrame?")
print(df_na.isnull())

# Verificar si hay valores nulos en alguna columna específica
print("\n¿Hay valores nulos en la columna 'edad'?")
print(df_na['edad'].isnull())

# Verificar si alguna fila tiene algún valor nulo
print("\n¿Cada fila tiene al menos un valor nulo?")
print(df_na.isnull().any(axis=1))

# Verificar si todas las columnas tienen al menos un valor nulo
print("\n¿Cada columna tiene al menos un valor nulo?")
print(df_na.isnull().any())
¿Hay valores nulos en el DataFrame?
   nombre   edad  ciudad  puntuacion
0   False  False   False       False
1   False  False    True       False
2   False   True   False       False
3    True  False   False        True
4   False  False   False       False

¿Hay valores nulos en la columna 'edad'?
0    False
1    False
2     True
3    False
4    False
Name: edad, dtype: bool

¿Cada fila tiene al menos un valor nulo?
0    False
1     True
2     True
3     True
4    False
dtype: bool

¿Cada columna tiene al menos un valor nulo?
nombre        True
edad          True
ciudad        True
puntuacion    True
dtype: bool

Número de datos faltantes

# Contar el número total de valores nulos en el DataFrame
print(f"Número total de valores faltantes: {df_na.isnull().sum().sum()}")

# Contar valores nulos por columna
print("\nValores faltantes por columna:")
print(df_na.isnull().sum())

# Contar valores nulos por fila
print("\nNúmero de valores faltantes por fila:")
print(df_na.isnull().sum(axis=1))

# Porcentaje de valores faltantes por columna
print("\nPorcentaje de valores faltantes por columna:")
print((df_na.isnull().sum() / len(df_na)) * 100)
Número total de valores faltantes: 4

Valores faltantes por columna:
nombre        1
edad          1
ciudad        1
puntuacion    1
dtype: int64

Número de valores faltantes por fila:
0    0
1    1
2    1
3    2
4    0
dtype: int64

Porcentaje de valores faltantes por columna:
nombre        20.0
edad          20.0
ciudad        20.0
puntuacion    20.0
dtype: float64
Identificar la cantidad y distribución de los valores faltantes es el primer paso para decidir cómo tratarlos. Si hay demasiados valores faltantes en una columna o fila, podría ser mejor eliminarla en lugar de intentar imputarla.

Eliminar datos faltantes

# Eliminar filas con valores nulos
df_sin_nulos_filas = df_na.dropna()
print("DataFrame después de eliminar filas con valores nulos:")
print(df_sin_nulos_filas)

# Eliminar filas donde falten todos los valores
df_sin_filas_all_na = df_na.dropna(how='all')
print("\nDataFrame después de eliminar filas donde todos los valores son nulos:")
print(df_sin_filas_all_na)  # En este caso, no hay filas donde todos los valores sean nulos

# Eliminar filas donde falten más de 2 valores
df_sin_filas_thresh = df_na.dropna(thresh=3)  # Al menos 3 valores no nulos
print("\nDataFrame después de eliminar filas con menos de 3 valores no nulos:")
print(df_sin_filas_thresh)

# Eliminar columnas con valores nulos
df_sin_nulos_columnas = df_na.dropna(axis=1)
print("\nDataFrame después de eliminar columnas con valores nulos:")
print(df_sin_nulos_columnas)  # En este caso, todas las columnas tienen al menos un valor nulo
DataFrame después de eliminar filas con valores nulos:
     nombre   edad  ciudad  puntuacion
0       Ana  28.00  Madrid        85.0
4  Fernanda  36.00  Madrid        89.0

DataFrame después de eliminar filas donde todos los valores son nulos:
     nombre   edad    ciudad  puntuacion
0       Ana  28.00    Madrid        85.0
1    Carlos  34.00      None        92.0
2     Diana    NaN  Valencia        78.0
3      None  42.00   Sevilla         NaN
4  Fernanda  36.00    Madrid        89.0

DataFrame después de eliminar filas con menos de 3 valores no nulos:
     nombre   edad    ciudad  puntuacion
0       Ana  28.00    Madrid        85.0
1    Carlos  34.00      None        92.0
2     Diana    NaN  Valencia        78.0
4  Fernanda  36.00    Madrid        89.0

DataFrame después de eliminar columnas con valores nulos:
Empty DataFrame
Columns: []
Index: [0, 1, 2, 3, 4]
La eliminación de filas o columnas con valores faltantes puede reducir significativamente el tamaño de tu conjunto de datos. Antes de aplicar esta técnica, asegúrate de que no estás perdiendo información valiosa. En algunos casos, es mejor reemplazar los valores faltantes en lugar de eliminarlos.

Reemplazar los datos faltantes

# Reemplazar valores nulos con un valor fijo
df_fill_fixed = df_na.copy()
df_fill_fixed.fillna(0)  # Reemplazar con 0
print("Reemplazar todos los valores nulos con 0:")
print(df_fill_fixed.fillna(0))

# Reemplazar valores nulos con diferentes valores por columna
valores = {
   'nombre': 'Desconocido',
   'edad': df_na['edad'].mean(),  # Media de las edades
   'ciudad': 'Sin especificar',
   'puntuacion': df_na['puntuacion'].median()  # Mediana de las puntuaciones
}
df_fill_dict = df_na.copy()
print("\nReemplazar valores nulos con valores específicos por columna:")
print(df_fill_dict.fillna(value=valores))

# Reemplazar valores nulos utilizando el método 'ffill' (forward fill)
df_ffill = df_na.copy()
print("\nReemplazar valores nulos con el valor anterior (forward fill):")
print(df_ffill.fillna(method='ffill'))

# Reemplazar valores nulos utilizando el método 'bfill' (backward fill)
df_bfill = df_na.copy()
print("\nReemplazar valores nulos con el valor siguiente (backward fill):")
print(df_bfill.fillna(method='bfill'))
Reemplazar todos los valores nulos con 0:
     nombre   edad    ciudad  puntuacion
0       Ana  28.00    Madrid        85.0
1    Carlos  34.00         0        92.0
2     Diana   0.00  Valencia        78.0
3         0  42.00   Sevilla         0.0
4  Fernanda  36.00    Madrid        89.0

Reemplazar valores nulos con valores específicos por columna:
       nombre   edad           ciudad  puntuacion
0         Ana  28.00           Madrid        85.0
1      Carlos  34.00  Sin especificar        92.0
2       Diana  35.00         Valencia        78.0
3 Desconocido  42.00          Sevilla        86.0
4    Fernanda  36.00           Madrid        89.0

Reemplazar valores nulos con el valor anterior (forward fill):
     nombre   edad    ciudad  puntuacion
0       Ana  28.00    Madrid        85.0
1    Carlos  34.00    Madrid        92.0
2     Diana  34.00  Valencia        78.0
3     Diana  42.00   Sevilla        78.0
4  Fernanda  36.00    Madrid        89.0

Reemplazar valores nulos con el valor siguiente (backward fill):
     nombre   edad    ciudad  puntuacion
0       Ana  28.00    Madrid        85.0
1    Carlos  34.00  Valencia        92.0
2     Diana  42.00  Valencia        78.0
3  Fernanda  42.00   Sevilla        89.0
4  Fernanda  36.00    Madrid        89.0
La elección del método para reemplazar valores faltantes depende del tipo de datos y del contexto. Por ejemplo, para series temporales, el método de propagación hacia adelante (forward fill) suele ser apropiado, mientras que para datos categóricos, reemplazar con el valor más frecuente puede ser una mejor opción.
# Imputación mediante interpolación para datos numéricos
df_interp = df_na.copy()
# Solo interpolamos columnas numéricas
cols_num = df_na.select_dtypes(include='number').columns
df_interp[cols_num] = df_interp[cols_num].interpolate(method='linear')
print("Valores numéricos interpolados linealmente:")
print(df_interp)

# Método más avanzado: imputación utilizando la estrategia KNN
# (Requiere scikit-learn)
from sklearn.impute import KNNImputer
import numpy as np

# Crear un DataFrame solo con columnas numéricas para KNN
df_num = df_na[['edad', 'puntuacion']].copy()
# Configurar el imputador KNN
imputer = KNNImputer(n_neighbors=2)
# Aplicar la imputación
df_num_imputed = pd.DataFrame(
   imputer.fit_transform(df_num),
   columns=df_num.columns,
   index=df_num.index
)
print("\nValores numéricos imputados con KNN:")
print(df_num_imputed)
Valores numéricos interpolados linealmente:
     nombre   edad    ciudad  puntuacion
0       Ana  28.00    Madrid        85.0
1    Carlos  34.00      None        92.0
2     Diana  38.00  Valencia        78.0
3      None  42.00   Sevilla        83.5
4  Fernanda  36.00    Madrid        89.0

Valores numéricos imputados con KNN:
    edad  puntuacion
0  28.00        85.0
1  34.00        92.0
2  32.00        78.0
3  42.00        84.5
4  36.00        89.0
La imputación mediante técnicas más sofisticadas como KNN puede proporcionar mejores estimaciones para los valores faltantes, especialmente cuando hay patrones o correlaciones entre las variables. Sin embargo, estas técnicas también pueden ser más intensivas computacionalmente.

Datos Únicos (Unique Values)

Identificar los valores únicos en un conjunto de datos es fundamental para comprender la distribución y variabilidad de las variables. Pandas proporciona métodos para obtener y analizar estos valores únicos.

# Crear un DataFrame de ejemplo
datos = {
   'nombre': ['Ana', 'Carlos', 'Diana', 'Eduardo', 'Fernanda', 'Ana'],
   'departamento': ['Ventas', 'IT', 'Marketing', 'IT', 'Ventas', 'Ventas'],
   'ciudad': ['Madrid', 'Barcelona', 'Madrid', 'Valencia', 'Madrid', 'Madrid'],
   'edad': [28, 34, 29, 42, 36, 28]
}

df = pd.DataFrame(datos)
print("DataFrame original:")
print(df)
DataFrame original:
     nombre departamento    ciudad  edad
0       Ana       Ventas    Madrid    28
1    Carlos          IT  Barcelona    34
2     Diana    Marketing    Madrid    29
3   Eduardo          IT   Valencia    42
4  Fernanda       Ventas    Madrid    36
5       Ana       Ventas    Madrid    28
# Obtener valores únicos en una columna
print("Valores únicos en la columna 'departamento':")
print(df['departamento'].unique())

# Contar el número de valores únicos
print("\nNúmero de valores únicos por columna:")
print(df.nunique())

# Contar la frecuencia de cada valor único
print("\nFrecuencia de cada valor en 'departamento':")
print(df['departamento'].value_counts())

# Porcentaje de cada valor único
print("\nPorcentaje de cada valor en 'departamento':")
print(df['departamento'].value_counts(normalize=True) * 100)

# Valores únicos en todas las columnas
print("\nLista de valores únicos por columna:")
for columna in df.columns:
   valores = df[columna].unique()
   print(f"{columna}: {valores}")
Valores únicos en la columna 'departamento':
['Ventas' 'IT' 'Marketing']

Número de valores únicos por columna:
nombre          5
departamento    3
ciudad          3
edad            4
dtype: int64

Frecuencia de cada valor en 'departamento':
Ventas       3
IT           2
Marketing    1
Name: count, dtype: int64

Porcentaje de cada valor en 'departamento':
Ventas       50.0
IT           33.333333
Marketing    16.666667
Name: count, dtype: float64

Lista de valores únicos por columna:
nombre: ['Ana' 'Carlos' 'Diana' 'Eduardo' 'Fernanda']
departamento: ['Ventas' 'IT' 'Marketing']
ciudad: ['Madrid' 'Barcelona' 'Valencia']
edad: [28 34 29 42 36]
El método value_counts() es especialmente útil para variables categóricas, ya que permite identificar rápidamente las categorías más frecuentes y detectar posibles desequilibrios en los datos.

Datos Duplicados

Los datos duplicados pueden afectar significativamente los resultados del análisis. Pandas ofrece herramientas para identificar y eliminar duplicados en un DataFrame.

# Crear un DataFrame con filas duplicadas
datos_dup = {
   'nombre': ['Ana', 'Carlos', 'Diana', 'Eduardo', 'Ana', 'Carlos'],
   'departamento': ['Ventas', 'IT', 'Marketing', 'IT', 'Ventas', 'IT'],
   'ciudad': ['Madrid', 'Barcelona', 'Madrid', 'Valencia', 'Madrid', 'Barcelona']
}

df_dup = pd.DataFrame(datos_dup)
print("DataFrame con filas duplicadas:")
print(df_dup)
DataFrame con filas duplicadas:
   nombre departamento    ciudad
0     Ana       Ventas    Madrid
1  Carlos           IT Barcelona
2   Diana    Marketing    Madrid
3 Eduardo           IT  Valencia
4     Ana       Ventas    Madrid
5  Carlos           IT Barcelona
# Identificar filas duplicadas
print("Máscara de filas duplicadas:")
print(df_dup.duplicated())

# Mostrar solo las filas duplicadas
print("\nFilas duplicadas:")
print(df_dup[df_dup.duplicated()])

# Contar el número de duplicados
print("\nNúmero de filas duplicadas:", df_dup.duplicated().sum())

# Eliminar duplicados y mantener la primera aparición
df_sin_dup = df_dup.drop_duplicates()
print("\nDataFrame sin duplicados (mantiene primera aparición):")
print(df_sin_dup)

# Eliminar duplicados y mantener la última aparición
df_sin_dup_last = df_dup.drop_duplicates(keep='last')
print("\nDataFrame sin duplicados (mantiene última aparición):")
print(df_sin_dup_last)

# Eliminar todos los duplicados (incluyendo el original)
df_sin_dup_none = df_dup.drop_duplicates(keep=False)
print("\nDataFrame sin ninguna fila duplicada (elimina todas las instancias):")
print(df_sin_dup_none)
Máscara de filas duplicadas:
0    False
1    False
2    False
3    False
4     True
5     True
dtype: bool

Filas duplicadas:
   nombre departamento    ciudad
4     Ana       Ventas    Madrid
5  Carlos          IT  Barcelona

Número de filas duplicadas: 2

DataFrame sin duplicados (mantiene primera aparición):
   nombre departamento    ciudad
0     Ana       Ventas    Madrid
1  Carlos           IT Barcelona
2   Diana    Marketing    Madrid
3 Eduardo           IT Barcelona

DataFrame sin duplicados (mantiene última aparición):
   nombre departamento    ciudad
2   Diana    Marketing    Madrid
3 Eduardo           IT  Valencia
4     Ana       Ventas    Madrid
5  Carlos           IT Barcelona

DataFrame sin ninguna fila duplicada (elimina todas las instancias):
   nombre departamento    ciudad
2   Diana    Marketing    Madrid
3 Eduardo           IT  Valencia

Duplicados por Columna

# Identificar duplicados basados en columnas específicas
print("Identificar duplicados solo por nombre:")
print(df_dup.duplicated(subset=['nombre']))

# Eliminar duplicados basados en columnas específicas
df_sin_dup_cols = df_dup.drop_duplicates(subset=['nombre'])
print("\nDataFrame sin duplicados por nombre:")
print(df_sin_dup_cols)

# Ejemplo con múltiples columnas
print("\nIdentificar duplicados por combinación de nombre y departamento:")
print(df_dup.duplicated(subset=['nombre', 'departamento']))

# Eliminar duplicados basados en múltiples columnas
df_sin_dup_multi = df_dup.drop_duplicates(subset=['nombre', 'departamento'])
print("\nDataFrame sin duplicados por nombre y departamento:")
print(df_sin_dup_multi)
Identificar duplicados solo por nombre:
0    False
1    False
2    False
3    False
4     True
5     True
dtype: bool

DataFrame sin duplicados por nombre:
   nombre departamento    ciudad
0     Ana       Ventas    Madrid
1  Carlos           IT Barcelona
2   Diana    Marketing    Madrid
3 Eduardo           IT  Valencia

Identificar duplicados por combinación de nombre y departamento:
0    False
1    False
2    False
3    False
4     True
5     True
dtype: bool

DataFrame sin duplicados por nombre y departamento:
   nombre departamento    ciudad
0     Ana       Ventas    Madrid
1  Carlos           IT Barcelona
2   Diana    Marketing    Madrid
3 Eduardo           IT  Valencia
Al eliminar duplicados, ten en cuenta que el resultado dependerá del parámetro keep. Con keep='first' (valor predeterminado) se mantiene la primera ocurrencia, con keep='last' se mantiene la última, y con keep=False se eliminan todas las filas duplicadas, incluida la original.

Conversión de Variables Categóricas a Numéricas

Muchos algoritmos de aprendizaje automático requieren que los datos de entrada sean numéricos. Sin embargo, en conjuntos de datos reales, es común encontrar variables categóricas. Pandas ofrece formas de convertir estas variables categóricas a representaciones numéricas, especialmente a través de la creación de variables dummy.

# Crear un DataFrame con variables categóricas
datos_cat = {
   'nombre': ['Ana', 'Carlos', 'Diana', 'Eduardo', 'Fernanda'],
   'departamento': ['Ventas', 'IT', 'Marketing', 'IT', 'Ventas'],
   'nivel_educativo': ['Universitario', 'Posgrado', 'Secundario', 'Universitario', 'Posgrado'],
   'salario': [50000, 65000, 45000, 70000, 60000]
}

df_cat = pd.DataFrame(datos_cat)
print("DataFrame original con variables categóricas:")
print(df_cat)
DataFrame original con variables categóricas:
     nombre departamento  nivel_educativo  salario
0       Ana       Ventas    Universitario    50000
1    Carlos           IT         Posgrado    65000
2     Diana    Marketing       Secundario    45000
3   Eduardo           IT    Universitario    70000
4  Fernanda       Ventas         Posgrado    60000

Variables Dummy (One-Hot Encoding)

La codificación one-hot convierte cada categoría en una nueva columna binaria (0 o 1), también conocida como "variable dummy". Pandas proporciona la función get_dummies() para realizar esta conversión de manera sencilla.

# Convertir 'departamento' a variables dummy
dummies_depto = pd.get_dummies(df_cat['departamento'], prefix='depto')
print("Variables dummy para 'departamento':")
print(dummies_depto)

# Concatenar las variables dummy al DataFrame original
df_dummies = pd.concat([df_cat, dummies_depto], axis=1)
print("\nDataFrame con variables dummy añadidas:")
print(df_dummies)
Variables dummy para 'departamento':
   depto_IT depto_Marketing  depto_Ventas
0         0               0             1
1         1               0             0
2         0               1             0
3         1               0             0
4         0               0             1

DataFrame con variables dummy añadidas:
     nombre departamento  nivel_educativo  salario  depto_IT  depto_Marketing  depto_Ventas
0       Ana       Ventas    Universitario    50000        0               0             1
1    Carlos           IT         Posgrado    65000        1               0             0
2     Diana    Marketing       Secundario    45000        0               1             0
3   Eduardo           IT    Universitario    70000        1               0             0
4  Fernanda       Ventas         Posgrado    60000        0               0             1
# Convertir múltiples columnas a variables dummy de una vez
df_dummies_all = pd.get_dummies(df_cat, columns=['departamento', 'nivel_educativo'])
print("DataFrame con todas las variables categóricas convertidas a dummy:")
print(df_dummies_all)
DataFrame con todas las variables categóricas convertidas a dummy:
     nombre  salario  departamento_IT  departamento_Marketing  departamento_Ventas  nivel_educativo_Posgrado  nivel_educativo_Secundario  nivel_educativo_Universitario
0       Ana    50000              0                     0                   1                        0                          0                              1
1    Carlos    65000              1                     0                   0                        1                          0                              0
2     Diana    45000              0                     1                   0                        0                          1                              0
3   Eduardo    70000              1                     0                   0                        0                          0                              1
4  Fernanda    60000              0                     0                   1                        1                          0                              0
Al utilizar get_dummies(), la columna original se elimina solo si se especifica en el parámetro columns. Si se aplica directamente sobre una columna y luego se concatena, se mantienen ambas versiones.

Problema de la Multicolinealidad

Cuando se utilizan variables dummy, se puede generar multicolinealidad perfecta, conocida como "trampa de las variables dummy". Para evitarla, se puede eliminar una de las categorías.

# Evitar la trampa de las variables dummy eliminando una categoría
df_dummies_drop = pd.get_dummies(df_cat, columns=['departamento'], drop_first=True)
print("Variables dummy con eliminación de la primera categoría:")
print(df_dummies_drop)
Variables dummy con eliminación de la primera categoría:
     nombre nivel_educativo  salario  departamento_Marketing  departamento_Ventas
0       Ana   Universitario    50000                      0                    1
1    Carlos        Posgrado    65000                      0                    0
2     Diana      Secundario    45000                      1                    0
3   Eduardo   Universitario    70000                      0                    0
4  Fernanda        Posgrado    60000                      0                    1
Usar drop_first=True elimina la primera categoría. La categoría eliminada se convierte en la "referencia" contra la cual se interpretan las demás. Por ejemplo, en este caso, "departamento_IT" se convierte en la referencia.

Codificación de Variables Ordinales en Pandas

Cuando las categorías tienen un orden natural (por ejemplo, "Bajo", "Medio", "Alto"), puede ser más apropiado usar una codificación ordinal utilizando un mapeo manual.

# Definir mapeo para codificación ordinal
mapeo_nivel = {
   'Secundario': 1,
   'Universitario': 2,
   'Posgrado': 3
}

# Aplicar mapeo
df_cat['nivel_educativo_cod'] = df_cat['nivel_educativo'].map(mapeo_nivel)
print("DataFrame con codificación ordinal para nivel educativo:")
print(df_cat)
DataFrame con codificación ordinal para nivel educativo:
     nombre departamento nivel_educativo  salario  nivel_educativo_cod
0       Ana       Ventas   Universitario    50000                    2
1    Carlos           IT        Posgrado    65000                    3
2     Diana    Marketing      Secundario    45000                    1
3   Eduardo           IT   Universitario    70000                    2
4  Fernanda       Ventas        Posgrado    60000                    3

Usando el tipo de datos categórico de Pandas

Otra opción es utilizar el tipo de datos categórico de Pandas, que permite especificar un orden si las categorías son ordinales.

# Crear una variable categórica con orden específico
categorias = ['Secundario', 'Universitario', 'Posgrado']
df_cat['nivel_cat'] = pd.Categorical(df_cat['nivel_educativo'], 
                                  categories=categorias, 
                                  ordered=True)

print("Columna categórica ordinal:")
print(df_cat[['nivel_educativo', 'nivel_cat']])

# Obtener códigos numéricos de la columna categórica
df_cat['nivel_cat_code'] = df_cat['nivel_cat'].cat.codes
print("\nCódigos numéricos de la columna categórica:")
print(df_cat[['nivel_educativo', 'nivel_cat', 'nivel_cat_code']])
Columna categórica ordinal:
  nivel_educativo     nivel_cat
0   Universitario Universitario
1        Posgrado      Posgrado
2      Secundario    Secundario
3   Universitario Universitario
4        Posgrado      Posgrado

Códigos numéricos de la columna categórica:
  nivel_educativo     nivel_cat  nivel_cat_code
0   Universitario Universitario               1
1        Posgrado      Posgrado               2
2      Secundario    Secundario               0
3   Universitario Universitario               1
4        Posgrado      Posgrado               2
El método cat.codes devuelve los códigos numéricos de una columna categórica, donde los códigos se asignan según el orden de las categorías (0 para la primera categoría, 1 para la segunda, etc.).

Ejercicios Prácticos

Notebooks interactivos en Google Colab donde podrás practicar los conceptos básicos de Pandas para ciencia de datos. Ideal para consolidar lo aprendido en este capítulo con ejercicios prácticos y ejemplos ejecutables

Referencias

Para aprender más sobre Numpy, recomendamos consultar estos recursos oficiales: