Ya hemos visto que el módulo
NumPy proporciona funciones y rutinas matemáticas para la manipulación de array y matrices de datos numéricos.
La librería
pandas de Python proporciona estructuras de datos de alto nivel y herramientas diseñadas específicamente para conseguir un análisis de los datos rápido y sencillo. * pandas está construida sobre la librería NumPy.
Resulta especialmente útil cuando hay que trabajar con:
Datos heterogéneos representados de forma tabular
Series temporales
Matrices
Permite el tratamiento de datos perdidos (missing data)
Operaciones aritméticas
Permite realizar operaciones del álgebra relacional (muy populares en el campo de las bases de datos)
In [1]:
import numpy as np
import pandas as pd
Ya vimos que NumPy ofrecía la estructura de datos
ndarray. Del mismo modo, pandas ofrece varias estructuras de datos que nos resultarán de mucha utilidad. Las más utilizadas son
Series y DataFrame.
DataFrame
La estructura
DataFrame está diseñada para manejar datos representados en forma de tabla. Esta será la estructura de datos más habitual. * Las filas están indexadas. * Las columnas están etiquetadas.
Un DataFrame puede verse como una tabla de SQL o una hoja de cálculo.
CREAR UN DATAFRAME
Hay varias formas de crear un DataFrame. Podemos crearlo indicando los datos que contiene y el nombre de las columnas.
In [5]:
# crear un array de tuplas
nombres = ['Ana','Rafa','Antonio','Pedro','Elena']
nacimientos = [968, 155, 77, 578, 100]
lista_datos = list(zip(nombres, nacimientos))
lista_datos
Out[5]:
In [6]:
# crear un DataFrame
tabla = pd.DataFrame( data = lista_datos, # lista de tuplas
columns = ['Nombres', 'Nacimientos'])
tabla
Out[6]:
In [7]:
# Añadir una nueva columna
tabla['nueva'] = [7, None, 4, None, None]
tabla
Out[7]:
También es posible crear un
DataFrame a partir de un diccionario. * Las claves del diccionario serán los nombres de las columnas * Los valores del diccionario constituirán las filas
In [176]:
datos = {"Coffee" : ['Espresso', 'Long Black', 'Flat White', 'Cappuccino', 'Affogato'],
"Water" : [ "No" , "Yes", "No" , "No" , "No" ],
"Milk" : ["No","No","Yes", "Yes - Frothy" ,"No"],
"Icecream" : ["No", "No","No","No",'Yes' ]
}
mi_tabla = pd.DataFrame(datos)
mi_tabla.tail()
Out[176]:
Podemos crear un DataFrame indexando las filas con un tipo de dato que no sea por defecto (int). Podemos crear un DataFrame cambiando la posición de las columnas, incluso con menos columnas.
In [177]:
datos = {"Coffee" : ['Espresso', 'Long Black', 'Flat White', 'Cappuccino', 'Affogato'],
"Water" : [ "No" , "Yes", "No" , "No" , "No" ],
"Milk" : ["No","No","Yes", "Yes - Frothy" ,"No"],
"Icecream" : ["No", "No","No","No",'Yes' ]
}
mi_tabla = pd.DataFrame(datos,
columns = ['Coffee', 'Water', 'Milk'],
index = [2001, 2003, 2002, 2005, 2010] )
mi_tabla
Out[177]:
Y si alguna columna no existe, el sistema pone NaN (not a number) para indicar que el valor no existe.
In [178]:
mi_tabla = pd.DataFrame(datos, columns = ['Coffee', 'Water', 'Milk', 'Price'],
index =[2001, 2003, 2002, 2005, 2010])
mi_tabla
Out[178]:
Para conocer el nombre de las columnas de un DataFrame usamos el atributo columns, mientras que para conocer el nombre de las filas usamos el atributo index.
In [179]:
mi_tabla.columns
Out[179]:
In [180]:
mi_tabla.index
Out[180]:
Podemos realizar algunas preguntas sobre los índices:
In [181]:
2005 in mi_tabla.index
Out[181]:
In [182]:
mi_tabla.values
Out[182]:
In [183]:
type(mi_tabla.values)
Out[183]:
In [184]:
s = pd.Series([10, 20, 30,40])
s
Out[184]:
In [185]:
precio = pd.Series([10, 20, 30,40],
index=['Espresso', 'Long Black', 'Flat White', 'Cappuccino'])
precio
Out[185]:
In [186]:
type(precio)
Out[186]:
In [187]:
mi_tabla
Out[187]:
In [188]:
# Acceso a la columna
mi_tabla.Coffee
Out[188]:
In [189]:
# También se puede acceder asi
mi_tabla [ 'Coffee' ]
Out[189]:
Para acceder a partes de un DataFrame, conjunto de filas, etc., se puede utilizar la misma notación empleada para los arrays de Numpy, pero esa opción es poco eficiente en el caso de DataFrames.
No hay que olvidar que estamos estudiando las herramientas que tiene Python para tratar de forma eficiente con grandes volúmenes de datos. La alternativa es utilizar las funciones at, iat, loc, iloc.
ix: similar a loc e iloc.
In [190]:
# Selección de las 3 primeras filas
# Notación típica en NumPy - Poco eficiente
mi_tabla[0:3]
Out[190]:
In [191]:
# Selección por etiqueta
# OJO: Ambos se incluyen
mi_tabla.loc[2001:2002]
Out[191]:
In [192]:
# Selección por etiqueta
mi_tabla.loc[2001:2002, ['Coffee', 'Milk']]
Out[192]:
In [193]:
# Selección por etiqueta - resultado un escalar
mi_tabla.loc[2001, 'Coffee']
Out[193]:
In [194]:
# Selección por etiqueta - resultado un escalar
mi_tabla.at[2001, 'Coffee'] # más eficiente
Out[194]:
In [195]:
# Acceso a la primera fila del DataFrame
# El resultado es una serie
mi_tabla.iloc[1]
Out[195]:
In [196]:
# Acceso a las filas con índices 1 y 2,
# Columnas desde aquella con índice 1 hasta el final.
mi_tabla.iloc[1:3,1:]
Out[196]:
In [197]:
print(mi_tabla)
# Acesso al valor de una casilla
mi_tabla.iloc[1,0]
Out[197]:
In [198]:
# Acesso al valor de una casilla (igual que el anterior)
mi_tabla.iat[1,0] # más rápida que la anterior
Out[198]:
In [199]:
mi_tabla.Price = .85
mi_tabla
Out[199]:
In [200]:
mi_tabla.Price = [.85, 0.95, 0.80, 1.10, 0.65]
mi_tabla
Out[200]:
Podemos calcular el precio medio del café.
In [201]:
medio_cafe = mi_tabla.Price.mean()
medio_cafe
Out[201]:
In [202]:
pp = mi_tabla.mean()
pp
Out[202]:
In [203]:
mi_tabla
Out[203]:
Las operaciones sobre DataFrames son vectorizadas, al igual que ocurría con las operaciones sobre los objetos ndarray de NumPy.
Podemos aumentar el precio del café 10 céntimos.
In [204]:
mi_tabla['nuevo_pvp'] = mi_tabla.Price + 0.10
mi_tabla
Out[204]:
BORRAR COLUMNAS DE UN DATAFRAME (POP Y DROP)
La función
pop borra la columna especificada del DataFrame y guarda la columna borrada en un objeto de tipo Serie.
Modifica el DataFrame
La función
drop permite borrar tanto filas como columnas.
No modifica el DataFrame. El resultado lo guarda en una copia.
No permite acceder a los elementos borrados.
In [205]:
# Borra la columna 'Price'
columna_price = mi_tabla.pop('Price')
columna_price
Out[205]:
In [206]:
type(columna_price)
Out[206]:
In [207]:
mi_tabla
Out[207]:
También es posible utilizar la función drop para eliminar tanto filas como columnas.
In [208]:
# Borramos las filas correspondientes a los índices 2005 y 2010
mi_tabla.drop([2005,2010])
mi_tabla
Out[208]:
In [209]:
# Borramos las columnas Water y Milk
# Hay que especificar el eje que contiene las etiquetas
otro = mi_tabla.drop(['Water', 'Milk'], 1)
otro
Out[209]:
In [210]:
# Nueva columna
mi_tabla['Chocolat'] = ['No', 'No', np.nan, 'Yes', 'No' ]
mi_tabla
Out[210]:
También podemos utilizar la función
insert para insertar la columna Price que hemos eliminado anteriormente en una determinada posición.
In [211]:
# Inserta la columna en la segunda posición
mi_tabla.insert(2, 'Price', columna_price)
mi_tabla
Out[211]:
COMBINAR DATAFRAMES
append, merge, join
Pandas provee varias formas de combinar Series y DataFrames.
Las filas de un DataFrame se pueden insertar mediante la función
append especificando un diccionario, donde la clave es el nombre de la columna.
Podemos insertar una fila parcial: Las columnas no especificadas pasan a tomar el valor NaN.
Peligro: se pierde el nombre de los índices, que pasan a ser enteros.
In [212]:
# Nueva fila (Total o parcial)
# -- se pierde el valor de los índices !!!!!!
# Crea un indice autonumerado
mi_tabla.append({ 'Coffee': 'Other',
'Water': 'No',
'Chocolat': 'Yes',
'Price' : 0.50 },
ignore_index = True)
Out[212]:
Para no perder el nombre de los índices, se realiza la operación append con un DataFrame en lugar de un diccionario:
In [213]:
# Creamos el data frame a insertar
otro_df = pd.DataFrame({ 'Coffee': 'Other',
'Water': 'No',
'Chocolat': 'Yes',
'Price' : 0.50 },
index = [2015])
# Insertamos el data frame mediante la función 'append'
mi_tabla.append(otro_df)
Out[213]:
In [214]:
mi_tabla
Out[214]:
Cuidado!, la función
append no modifica el objeto.
In [215]:
mi_tabla = mi_tabla.append(otro_df )
mi_tabla
Out[215]:
MERGE: La operación join al estilo del álgebra relacional
La función
merge permite combinar filas de dos o más tablas (DataFrames) basándose en una o varias claves. Trabaja de forma muy eficiente, de forma similar a las bases de datos relacionales tipo SQL. Hay tres tipos de merge:
interno (inner)
externo (outer)
cruzado(left, right)
In [216]:
# inner join on key
df1 = pd.DataFrame({'key': ['K0', 'K1', 'K1', 'K3'],
'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3']})
df2 = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'],
'A': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']})
result = pd.merge(df1, df2, on='key')
print (df1)
print (df2)
result
Out[216]:
In [217]:
# inner join on (key1 key2)
df1 = pd.DataFrame({'key1': ['K0', 'K0', 'K1', 'K2'],
'key2': ['K0', 'K1', 'K0', 'K1'],
'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3']})
df2 = pd.DataFrame({'key1': ['K0', 'K1', 'K1', 'K2'],
'key2': ['K0', 'K0', 'K0', 'K0'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']})
result = pd.merge( df1, df2,
on=['key1', 'key2'])
print (df1)
print (df2)
result
Out[217]:
left join: todas las filas de la tabla izquierda df1 tengan o no correspondencia con la tabla derecha. Las que no tengan correspondencia se rellenan con NaN.
In [218]:
result = pd.merge(df1, df2,
how='left',
on=['key1', 'key2'])
print (df1)
print (df2)
result
Out[218]:
outer join: Es la unión de left outer join y right outer join
In [219]:
result = pd.merge(df1, df2,
how='outer',
on=['key1', 'key2'])
result
Out[219]:
JOIN : Combinación de tablas por índices
La función join permite combinar dos tablas de dos formas:
índices sobre índices: mantiene la información de los índices.
columnas sobre índices.
Es posible realizar
**inner_join**
mediante**how = inner**
, left_join mediante
how = left,
right_join mediante
how = right.
In [220]:
otro_df = pd.DataFrame([3,2,5,3,2],
columns = ['Stars',],
index = [2001, 2002, 2005, 2015, 2016])
print(otro_df)
mi_tabla.join(otro_df)
Out[220]:
En el ejemplo anterior se han perdido los datos referentes al índice 2016. Para conservarlos es necesario indicar cómo queremos que se realice la operación
join. En este caso usaremos
how = outer para que se conserven todos los índices.
In [221]:
otro_df = pd.DataFrame( [3,2,5,3,2],
columns = ['Stars',],
index = [2001, 2002, 2005, 2015, 2016])
mi_tabla.join(otro_df, how = 'outer')
Out[221]:
Ahora veamos un ejemplo de combinación de columnas sobre índices:
In [222]:
otro_df = pd.DataFrame([[1,2],[3,4]],
columns = ['Val1','Val2'],
index = ['Cappuccino', 'Espresso'])
otro_df
Out[222]:
In [223]:
# Los valores de la columna Coffee hacen la función de índices del dataframe
mi_tabla.join(otro_df, on = 'Coffee', how = 'outer')
Out[223]:
In [ ]: