Explorando y analizando DataFrames con Pandas

Pandas es un paquete construido sobre la base de NumPy, incluye la implementación de la estructura DataFrame. Un DataFrame es, en esencia, un arreglo bidimensional con etiquetas para filas y columnas, típicamente las columnas contienen tipo de datos diferentes.

  1. Series

  2. DataFrame

  3. Índices

  4. Obtención de datos

  5. Se utiliza (&, |) en lugar de (and, or)

  6. Modificación de datos

  7. Apply

  8. One Hot Encoding

import numpy as np
import pandas as pd

Series

Un objecto de tipo Series es un arreglo de datos, parecido a un Array de numpy, que consta de índices y valores.

Aquí algunos enlaces de referencia:

  • https://pandas.pydata.org/pandas-docs/stable/reference/series.html#computations-descriptive-stats

  • https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.html

Una serie tiene varios métodos, como min, mean, std, entre muchos otros. Para crear una serie:

serie = pd.Series([0.25, 0.5, 0.75, 1.0])
print(serie)
print('Desviación estándar: ', serie.std())
0    0.25
1    0.50
2    0.75
3    1.00
dtype: float64
Desviación estándar:  0.3227486121839514

Una serie tiene valores e índices:

print('Valores: ', serie.values)
print('Índices: ', serie.index)
Valores:  [0.25 0.5  0.75 1.  ]
Índices:  RangeIndex(start=0, stop=4, step=1)

Filtrado de datos, retorna una Serie de valores booleanos:

serie > 0.5
0    False
1    False
2     True
3     True
dtype: bool
serie.isnull()
0    False
1    False
2    False
3    False
dtype: bool

A diferencia de los arreglos de Numpy, a una Serie se le puede asignar un indice de manera explícta:

serie = pd.Series([0.25, 0.5, 0.75, 1.0], index=['a', 'b', 'c', 'd']) 
print(serie['a':'c'])
a    0.25
b    0.50
c    0.75
dtype: float64

Se puede crear una Serie a partir de un diccionario (clave -> indice)

poblacion_dict = {'Chuquisaca': 626000, 
                  'La Paz': 26448193,
                  'Cochabamba': 2883000,
                  'Oruro': 538000,
                  'Potosí': 887000,
                  'Tarija': 563000,
                  'Santa Cruz': 3225000,
                  'Beni': 468000,
                  'Pando': 144000 }
poblacion = pd.Series(poblacion_dict)
poblacion
Chuquisaca      626000
La Paz        26448193
Cochabamba     2883000
Oruro           538000
Potosí          887000
Tarija          563000
Santa Cruz     3225000
Beni            468000
Pando           144000
dtype: int64

Otros ejemplos de creación de Series

serie = pd.Series(5, index=[100, 200, 300])
serie
100    5
200    5
300    5
dtype: int64

Selección de claves del diccionario (solo se crea un serie con una parte del diccionario)

serie = pd.Series({2:'a', 1:'b', 3:'c'}, index=[3, 2])
serie
3    c
2    a
dtype: object

Dataframes

Un DataFrame es un arreglo bi-dimensional formado por una secuencia de Series con la misma cantidad de elementos y con el mismo índice. Es decir: es como un diccionario de Series del mismo tamaño y con los mismos índices. Un DataFrame permite asignar nombres a las columnas.

extension_departamentos_Bolivia_dict = {'Chuquisaca': 51514, 
                    'La Paz': 133985,
                    'Cochabamba': 55631,
                    'Oruro': 55588,
                    'Potosí': 117218,
                    'Tarija': 37623,
                    'Santa Cruz': 370621,
                    'Beni': 213564
                  }
extension_departamentos_Serie = pd.Series(extension_departamentos_Bolivia_dict)
extension_departamentos_Serie
Chuquisaca     51514
La Paz        133985
Cochabamba     55631
Oruro          55588
Potosí        117218
Tarija         37623
Santa Cruz    370621
Beni          213564
dtype: int64

Creación a partir de dos Series que tiene el mismo index (aunque los indices no estén en el mismo order o incluso falten datos en algunas de las Series)

datos_bolivia = pd.DataFrame({'poblacion': poblacion, 'extension': extension_departamentos_Serie})
datos_bolivia
poblacion extension
Beni 468000 213564.0
Chuquisaca 626000 51514.0
Cochabamba 2883000 55631.0
La Paz 26448193 133985.0
Oruro 538000 55588.0
Pando 144000 NaN
Potosí 887000 117218.0
Santa Cruz 3225000 370621.0
Tarija 563000 37623.0

Tanto las filas como las columnas tienen asociado un índice

print(datos_bolivia.index)
print(datos_bolivia.columns)
Index(['Beni', 'Chuquisaca', 'Cochabamba', 'La Paz', 'Oruro', 'Pando',
       'Potosí', 'Santa Cruz', 'Tarija'],
      dtype='object')
Index(['poblacion', 'extension'], dtype='object')

Se puede ver a un DataFrame como un diccionario de Series (columnas)

datos_bolivia['poblacion']
Beni            468000
Chuquisaca      626000
Cochabamba     2883000
La Paz        26448193
Oruro           538000
Pando           144000
Potosí          887000
Santa Cruz     3225000
Tarija          563000
Name: poblacion, dtype: int64

Otras maneras de crear un DataFrame: si no se provee un índice se crea una secuencia de numeros que empieza en 0.

data = pd.DataFrame(columns=['a','b'], data=[[1, 45], [87, 96], [125, 13], [135, 789]])
data
a b
0 1 45
1 87 96
2 125 13
3 135 789

Lista de diccionarios (las claves son los nombres de las columnas)

data = pd.DataFrame([{'a': 1, 'b': 2}, {'b': 3, 'c': 4}])
data
a b c
0 1.0 2 NaN
1 NaN 3 4.0

Información general de un DataFrame

datos_bolivia.shape
(9, 2)
datos_bolivia.head(5)
poblacion extension
Beni 468000 213564.0
Chuquisaca 626000 51514.0
Cochabamba 2883000 55631.0
La Paz 26448193 133985.0
Oruro 538000 55588.0
datos_bolivia.tail(5)
poblacion extension
Oruro 538000 55588.0
Pando 144000 NaN
Potosí 887000 117218.0
Santa Cruz 3225000 370621.0
Tarija 563000 37623.0
datos_bolivia.size
18
datos_bolivia.info()
<class 'pandas.core.frame.DataFrame'>
Index: 9 entries, Beni to Tarija
Data columns (total 2 columns):
 #   Column     Non-Null Count  Dtype  
---  ------     --------------  -----  
 0   poblacion  9 non-null      int64  
 1   extension  8 non-null      float64
dtypes: float64(1), int64(1)
memory usage: 516.0+ bytes
# la función describe() devuele un DataFrame con indicadores para cada una de las columnas
datos_bolivia.describe()
poblacion extension
count 9.000000e+00 8.000000
mean 3.975799e+06 129468.000000
std 8.499862e+06 113904.633998
min 1.440000e+05 37623.000000
25% 5.380000e+05 54569.500000
50% 6.260000e+05 86424.500000
75% 2.883000e+06 153879.750000
max 2.644819e+07 370621.000000

Indices

Un Index es el mecanismo para referenciar datos en las Series y los DataFrames. Un Index object es un conjunto ordenado de valores

indA = pd.Index([1, 3, 5, 7, 9]) 
indB = pd.Index([2, 3, 5, 7, 11])
print(indA.union(indB))
print(indA.intersection(indB))
print(indA.difference(indB))
Int64Index([1, 2, 3, 5, 7, 9, 11], dtype='int64')
Int64Index([3, 5, 7], dtype='int64')
Int64Index([1, 9], dtype='int64')

Extracción de datos

Extraer datos de un DataFrame o una serie.

datos_bolivia = pd.DataFrame(data={'poblacion':poblacion, 'extension':extension_departamentos_Serie})
datos_bolivia
poblacion extension
Beni 468000 213564.0
Chuquisaca 626000 51514.0
Cochabamba 2883000 55631.0
La Paz 26448193 133985.0
Oruro 538000 55588.0
Pando 144000 NaN
Potosí 887000 117218.0
Santa Cruz 3225000 370621.0
Tarija 563000 37623.0

Un DataFrame es como diccionario de Series (columnas) en el cual se puede extraer y modificar datos

datos_bolivia['poblacion']
datos_bolivia[['poblacion','extension']]
datos_bolivia['constante'] = 1
datos_bolivia['densidad'] = datos_bolivia['poblacion'] / datos_bolivia['extension']
datos_bolivia
poblacion extension constante densidad
Beni 468000 213564.0 1 2.191381
Chuquisaca 626000 51514.0 1 12.152036
Cochabamba 2883000 55631.0 1 51.823624
La Paz 26448193 133985.0 1 197.396671
Oruro 538000 55588.0 1 9.678348
Pando 144000 NaN 1 NaN
Potosí 887000 117218.0 1 7.567097
Santa Cruz 3225000 370621.0 1 8.701612
Tarija 563000 37623.0 1 14.964251
datos_bolivia['capital'] =  pd.Series(
    {'Chuquisaca': 'Sucre', 
    'La Paz': 'Murillo',
    'Cochabamba': 'Cercado',
    'Oruro': 'Cercado',
    'Potosí': 'Potosí',
    'Tarija': 'Tarija',
    'Santa Cruz': 'Santa Cruz de la Sierra',
    'Pando': 'Cobija',
    'Beni': 'Trinidad' })
datos_bolivia
poblacion extension constante densidad capital
Beni 468000 213564.0 1 2.191381 Trinidad
Chuquisaca 626000 51514.0 1 12.152036 Sucre
Cochabamba 2883000 55631.0 1 51.823624 Cercado
La Paz 26448193 133985.0 1 197.396671 Murillo
Oruro 538000 55588.0 1 9.678348 Cercado
Pando 144000 NaN 1 NaN Cobija
Potosí 887000 117218.0 1 7.567097 Potosí
Santa Cruz 3225000 370621.0 1 8.701612 Santa Cruz de la Sierra
Tarija 563000 37623.0 1 14.964251 Tarija

Un DataFrame es también como un arreglo bidimensional (una matriz de Series) Soporta indices, slicing, filtering empleando los indices explicitos (iloc usa indices numéricos implicitos). El primer valor de la matriz hace referencia a las filas

  • https://railsware.com/blog/python-for-machine-learning-indexing-and-slicing-for-lists-tuples-strings-and-other–sequential-types/

datos_bolivia.loc['Beni']
poblacion      468000
extension    213564.0
constante           1
densidad     2.191381
capital      Trinidad
Name: Beni, dtype: object
datos_bolivia.loc['Beni':'Oruro']
poblacion extension constante densidad capital
Beni 468000 213564.0 1 2.191381 Trinidad
Chuquisaca 626000 51514.0 1 12.152036 Sucre
Cochabamba 2883000 55631.0 1 51.823624 Cercado
La Paz 26448193 133985.0 1 197.396671 Murillo
Oruro 538000 55588.0 1 9.678348 Cercado
datos_bolivia['poblacion'] > 2000000
Beni          False
Chuquisaca    False
Cochabamba     True
La Paz         True
Oruro         False
Pando         False
Potosí        False
Santa Cruz     True
Tarija        False
Name: poblacion, dtype: bool
datos_bolivia['extension'].isnull()
Beni          False
Chuquisaca    False
Cochabamba    False
La Paz        False
Oruro         False
Pando          True
Potosí        False
Santa Cruz    False
Tarija        False
Name: extension, dtype: bool

Se utiliza (&, |) en lugar de (and, or)

datos_bolivia.loc[(datos_bolivia['poblacion'] > 2000000) & (datos_bolivia['extension']> 60000.0), ['poblacion','densidad'] ]
datos_bolivia
poblacion extension constante densidad capital
Beni 468000 213564.0 1 2.191381 Trinidad
Chuquisaca 626000 51514.0 1 12.152036 Sucre
Cochabamba 2883000 55631.0 1 51.823624 Cercado
La Paz 26448193 133985.0 1 197.396671 Murillo
Oruro 538000 55588.0 1 9.678348 Cercado
Pando 144000 NaN 1 NaN Cobija
Potosí 887000 117218.0 1 7.567097 Potosí
Santa Cruz 3225000 370621.0 1 8.701612 Santa Cruz de la Sierra
Tarija 563000 37623.0 1 14.964251 Tarija

Modificación de datos

Elimina todos los datos de una columna

datos_bolivia.drop(columns=['constante'], inplace=True)
datos_bolivia
poblacion extension densidad capital
Beni 468000 213564.0 2.191381 Trinidad
Chuquisaca 626000 51514.0 12.152036 Sucre
Cochabamba 2883000 55631.0 51.823624 Cercado
La Paz 26448193 133985.0 197.396671 Murillo
Oruro 538000 55588.0 9.678348 Cercado
Pando 144000 NaN NaN Cobija
Potosí 887000 117218.0 7.567097 Potosí
Santa Cruz 3225000 370621.0 8.701612 Santa Cruz de la Sierra
Tarija 563000 37623.0 14.964251 Tarija

Eliminar los datos faltantes (los que son NaN)

datos_bolivia.dropna(how='any')
poblacion extension densidad capital
Beni 468000 213564.0 2.191381 Trinidad
Chuquisaca 626000 51514.0 12.152036 Sucre
Cochabamba 2883000 55631.0 51.823624 Cercado
La Paz 26448193 133985.0 197.396671 Murillo
Oruro 538000 55588.0 9.678348 Cercado
Potosí 887000 117218.0 7.567097 Potosí
Santa Cruz 3225000 370621.0 8.701612 Santa Cruz de la Sierra
Tarija 563000 37623.0 14.964251 Tarija
datos_bolivia.loc['Pando', 'densidad'] = datos_bolivia.loc['Pando', 'poblacion'] / datos_bolivia.loc['Pando', 'extension']
datos_bolivia
poblacion extension densidad capital
Beni 468000 213564.0 2.191381 Trinidad
Chuquisaca 626000 51514.0 12.152036 Sucre
Cochabamba 2883000 55631.0 51.823624 Cercado
La Paz 26448193 133985.0 197.396671 Murillo
Oruro 538000 55588.0 9.678348 Cercado
Pando 144000 NaN NaN Cobija
Potosí 887000 117218.0 7.567097 Potosí
Santa Cruz 3225000 370621.0 8.701612 Santa Cruz de la Sierra
Tarija 563000 37623.0 14.964251 Tarija

Apply

Appy aplica una función que recibe como argumento a cada una de las columnas (o filas) de un DataFrame. Modifica el DataFrame existente.

axis=0 es la opción por defecto, significa que se recorrerá el DataFrame por las columnas (similar a recorrer una matriz por columas). Si axis=1 el DataFrame se recorrerá por sus filas.

datos_bolivia_extension_reducida_a_la_mitad = datos_bolivia.apply(lambda x: x['extension']/2, axis=1)
datos_bolivia_extension_reducida_a_la_mitad
Beni          106782.0
Chuquisaca     25757.0
Cochabamba     27815.5
La Paz         66992.5
Oruro          27794.0
Pando              NaN
Potosí         58609.0
Santa Cruz    185310.5
Tarija         18811.5
dtype: float64

One Hot Encoding

Conversión de valores numéricos y nominales en categorías y luego las categorías en valores numéricos. Necesario cuando el algoritmo de aprendizaje automático no es capaz de trabajar con valores nominales o contínuos

Obtener los códigos de una variable nominal

datos_bolivia['capital'].astype('category').cat.codes
Beni          7
Chuquisaca    5
Cochabamba    0
La Paz        2
Oruro         0
Pando         1
Potosí        3
Santa Cruz    4
Tarija        6
dtype: int8

Obtener el vector One Hot Encoding

pd.get_dummies(datos_bolivia,columns=['capital'])
poblacion extension densidad capital_Cercado capital_Cobija capital_Murillo capital_Potosí capital_Santa Cruz de la Sierra capital_Sucre capital_Tarija capital_Trinidad
Beni 468000 213564.0 2.191381 0 0 0 0 0 0 0 1
Chuquisaca 626000 51514.0 12.152036 0 0 0 0 0 1 0 0
Cochabamba 2883000 55631.0 51.823624 1 0 0 0 0 0 0 0
La Paz 26448193 133985.0 197.396671 0 0 1 0 0 0 0 0
Oruro 538000 55588.0 9.678348 1 0 0 0 0 0 0 0
Pando 144000 NaN NaN 0 1 0 0 0 0 0 0
Potosí 887000 117218.0 7.567097 0 0 0 1 0 0 0 0
Santa Cruz 3225000 370621.0 8.701612 0 0 0 0 1 0 0 0
Tarija 563000 37623.0 14.964251 0 0 0 0 0 0 1 0