Ciencia de datos con Python
Pandas
José Ignacio Merino
Introducción
! Pandas es una librería para el manejo de datos
! Equivale a un Excel en el sentido que agrupa datos en filas/columnas, filtra, hace operaciones básicas con los
datos, lee datos, los exporta, …
! Para instalarlo “pip install pandas”
! Para usarlo “import pandas as pd” (suele ser lo normal ponerle el alias de pd)
Dataframes
! Un Dataframe es una agrupación de los datos en filas y columnas
! La forma más fácil de crear un dataframe es con series
! numeros = [Link]({"Numeros": [1, 2, 3, 4], "Cuadrados": [1, 4, 9, 16]})
! Para importar datos e un fichero
! datosElectricos = pd.read_csv("[Link]
Brutos_Ago2022.csv", encoding = "ISO-8859-1", skiprows=3, sep=";", decimal=",")
! Puedo obtener una sumarización de los datos con
! [Link]()
! Describe() no da información sobre columnas que no sean numéricas, para incluirlas
! [Link](include=“all")
! Puedo ver los primeros/últimos datos con
! [Link]()
! [Link]()
Dataframes
! Algunos parámetros para leer CSVs ([Link]
! sep=“,” -> Separador de datos
! decimal=“.” -> Separador decimal
! thousands=“_” -> Separador de miles
! header=True -> Si la primera fila de datos es una cabecera o por el contrario son datos
! skiprows=0 -> Número de líneas iniciales a saltarse
! skipfooter=0 -> Número de líneas finales a saltarse
! nrows=0 -> Número de líneas a leer
! names = [] -> Lista con textos para usarlos como nombres de las columnas
! usecols = [] -> Lista con los índices de las columnas que se quieren importar
! true_values/false_falues -> Valor que es sustituido por True/False
! na_values -> Valor que es sustituido por NA
! encoding = “utf-8” -> Codificación usada
! …
Dataframes
! Para obtener cuánto ocupa en memoria e información de los tipos
! [Link]()
! Para obtener los tipos de cada columna
! [Link]
! Para obtener los nombres de las columnas
! [Link]
! Para obtener el número de elementos en cada columna
! [Link]()
! Para obtener el número de elementos distintos en cada columna
! [Link]()
! Para obtener varios elemento
! [Link]()
! [Link]()
! [Link]()
! [Link]()
! [Link]()
Dataframes
! En qué filas ocurre el valor máximo/mínimo
! .idxmax()/.idxmin() (si hay valores de tipo object, falla)
! Más métricas
! .abs() -> Valor absoluto
! .std() -> Desviación estándard
! .var() -> Varianza
! .cov() -> Covarianza
! .corr() -> Correlación
Dataframes
! Para obtener una columna:
! datosElectricos["OTR_PRI"] -> Este siempre funciona
! datosElectricos.OTR_PRI -> Obviamente este no funciona si hay espacios o puntos en el nombre
! Para obtener filas
! datosElectricos[10:20]
! Podemos combinar ambos
! datosElectricos[10:20]["OTR_PRI"]
! datosElectricos["OTR_PRI"][10:20]
! Podemos aplicar distintas operaciones
! datosElectricos["OTR_PRI"][10:20].max()
! datosElectricos["OTR_PRI"][10:20].min()
! datosElectricos["OTR_PRI"][10:20].describe()
! datosElectricos["OTR_PRI"][10:20].idxmin()
Dataframes
! Para obtener 2 elementos de columnas
! datosElectricos[["OTR_PRI", “OTR_EXTR"]]
! Para obtener columnas con un patrón
! Buscar el patrón en un bucle con
! "Juan" in "Hola, Juan. ¿Cómo estás?" -> True
! "Hola, Juan. ¿Cómo estás?".count("Juan") -> 1
! "Hola, Juan. ¿Cómo estás?".index("Juan") -> 6 (El problema de esto es que da excepción si no se encuentra)
! Primera forma:
! columnas = []
! for columna in [Link]:
! if "PRI" in columna:
! [Link](columna)
! datosElectricos[columnas]
! Segunda forma:
! columnas = [columna for columna in [Link] if "PRI" in columna]
! datosElectricos[columnas]
Dataframes
! Podemos acceder a métodos para una columna
! str -> Accede a operaciones de cadena (son distintas operaciones de cadena que las normales)
! datosElectricos[“Grupo"].[Link]() -> "Juan".lower() sí existe
! datosElectricos[“Grupo"].[Link]("M") -> Esta operación no existe "Juan".contains("J") da error
! datosElectricos[“Grupo"].[Link]()
! datosElectricos["Grupo"].[Link]("2009")
! datosElectricos["Grupo"].[Link]("M") -> Divide una cadena en un array cada aparición de “M”
! datosElectricos["Grupo"].[Link]("M", "j") -> No cambia los datos en el df sólo los devuelve
cambiados
! datosElectricos["Grupo"].[Link]("2009") -> Nº de aparicios de una cadena
! También acepta expresiones regulares:
! datosElectricos["Grupo"].[Link]('([0-9]+)', expand=False)
Dataframes
! Podemos acceder a métodos para una columna
! array -> Devuelve un objeto de PandasArray con los valores
! value -> Devuelve una lista con los valores (mejor usar este, lo que devuelve es más estándar)
! datosElectricos["Grupo"].values
! datosElectricos["Grupo"].array
! at -> Devuelve un elemento
! datosElectricos[“Grupo"].at[10]
! datosElectricos["Grupo"].at[1] ="Hola" -> Cambia el elemento
! hasnans -> Devuelve si tiene valores NA la columna
! datosElectricos["Grupo"].hasnans
! is_unique -> Devuelve True si todos los elementos de la serie son distintos
! datosElectricos["Grupo"].is_unique
Dataframes
! Podemos acceder a métodos para una columna
! astype() -> Para cambiar el tipo de datos de uno a otro
! datosElectricos["OTR_PRI"].astype("int32")
! En caso que no se pueda convertir un valor, devolverá un error
! Para que no devuelva error y lo ignore:
! datosElectricos["Grupo"].astype("int32", errors = "ignore")
! Esto no cambia la columna original, tan sólo la devuelve cambiada al tipo indicado
! Para cambiarla:
! datosElectricos["OTR_PRI"] = datosElectricos["OTR_PRI"].astype("int32")
! between() -> Para obtener datos desde-hasta un valor (pero mejor usar otra sintaxis que veremos más
adelante)
! datosElectricos["OTR_PRI"].between(0, 100)
Dataframes
! plot() -> Dibuja el gráfico
! datosElectricos["OTR_PRI"].plot()
! datosElectricos[["OTR_PRI", "OTR_PRI.1", "OTR_PRI.2"]].plot()
! [Link]() -> Gráfico de áreas
! datosElectricos["OTR_PRI"].[Link]()
! [Link]() -> Gráfico de barras
! datosElectricos["OTR_PRI"].[Link]()
! [Link]() -> Gráfico de barras
! datosElectricos["OTR_PRI"].[Link]()
! [Link]() -> Gráfico de barras y bigotes
! datosElectricos["OTR_PRI"].[Link]()
! datosElectricos[["OTR_PRI", "OTR_PRI.1", "OTR_PRI.2"]].[Link]()
! hist() -> Histograma
! datosElectricos["OTR_PRI"].hist()
Dataframes
! count() -> Para contar cuántos valores no NA hay
! datosElectricos["OTR_PRI"].count()
! value_counts() -> Para contar cuantos valores distintos de cada valor hay
! datosElectricos["OTR_PRI"].value_counts()
! sort_values() -> Devuelve los valores ordenados
! datosElectricos["OTR_PRI"].sort_values() -> Ascendiente
! datosElectricos["OTR_PRI"].sort_values(ascending=False) -> Descendiente
!
Filtrado y manipulación de dataframes
! Podemos acceder a métodos para una columna
! fillna() -> Rellena los valores NA con el valor especificado
! datosElectricos["OTR_PRI"].fillna(0)
! interpolate() -> Rellena los valores NA interpolando valores anterior y siguiente. Por defecto usa un método linear,
pero tiene otras opciones de interpolación (cuidado aquí para llevar el modelo a producción)
! datosElectricos["OTR_PRI"].interpolate()
! drop() -> Devuelve la columna con el dato especificado eliminado
! datosElectricos["OTR_PRI"].drop(1)
! dropna() -> Devuelve la columna sin NAs (cuidado porque un NA podría estar en distintas posiciones, es mejor hacer
eso a nivel de dataframe)
! datosElectricos["OTR_PRI"].dropna()
! drop_duplicates() -> Devuelve la columna sin duplicados
! datosElectricos["OTR_PRI"].drop_duplicates()
Filtrado y manipulación de dataframes
! Para hacer operaciones entre series
! datosElectricos["OTR_PRI"] + 1000
! datosElectricos["OTR_PRI"] + datosElectricos["OTR_PRI.1"]
! datosElectricos["OTR_PRI"] / 1000
! datosElectricos["OTR_PRI"] / datosElectricos["OTR_PRI.1"]
! Para crear una nueva columna
! datosElectricos["division"] = datosElectricos["OTR_PRI"] / datosElectricos["OTR_PRI.1"]
! datosElectricos["todounos"] = 1
! Si queremos redondear
! datosElectricos[“division"].round(2)
! Para borrar una columna
! del datosElectricos["division"]
Filtrado y manipulación de dataframes
! Para el filtrado:
! datosElectricos[datosElectricos["OTR_PRI"] > 80]
! datosElectricos[(datosElectricos["OTR_PRI"] > 80) & (datosElectricos["OTR_PRI"] < 90)]
! datosElectricos[(datosElectricos["OTR_PRI"] < 80) | (datosElectricos["OTR_PRI"] > 90)]
! Puedo concatenar comandos
! datosElectricos[(datosElectricos["OTR_PRI"] > 80) & (datosElectricos["OTR_PRI"] < 90)]["Grupo"]
! Podría con idxmin()/idxmax() saber en qué fila está el elemento máximo o mínimo
! datosElectricos[(datosElectricos["OTR_PRI"] > 80) & (datosElectricos["OTR_PRI"] < 90)]
["OTR_PRI.1"].idxmin()
! Podría añadir una columna de True/False ante una condición
! datosElectricos["menores_80"] = datosElectricos["OTR_PRI"] > 80
Filtrado y manipulación de dataframes
! Para sacar las columnas:
! [Link]
! Para cambiar el nombre de una columna
! [Link](columns = {"Grupo":"MesYAnno"})
! Para cambiar el nombre de una columna en el propio dataset
! [Link](columns = {"Grupo":"MesYAnno"}, inplace=True)
!
Algunos ejercicios
! Con el dataset del consumo eléctrico por sectores
! Separar la fecha en dos columnas: el mes y el año
! Pista: En la propiedad str de la columna grupo, se permite el slicing ([0:4] ó [-2:])
! Las columnas han de ser numéricas
Algunos ejercicios
! Con el dataset de alquiler de bicicletas diario ([Link]
! Leerlo
! Hacer un dataset con todas las columnas menos “instant”
! ¿Cuántos valores distintos de meses hay?
! Dibujar el número total de alquileres
! Dibujar el histograma de temperaturas
! Volver a poner las temperaturas en una columna con sus valores normales
! ¿Cuál fue el día que más bicicletas se alquilaron? ¿Y el día que menos?
! ¿Cuál fue el día de la serie histórica que mayor temperatura se registró?
! Obtener los datos en el que el número de bicicletas alquiladas esté por encima de la media
! Ordenar los valores ascendentemente por temperatura
Filtrado y manipulación de dataframes
! Podemos hacer agregaciones con .agg()
! dfAnnos = [Link]("anno").agg(["sum", "mean"])
! Con “sum” y “mean”, indicamos las operaciones que queremos hacer (hay también “count”, “min”, “max”)
! Aquí pasan varias cosas:
! Los índices de filas cambian y ya no son 1, 2, 3, … sino los años (que es el valor por el que hemos agrupado)
! Las columnas tienen subcolumnas
! dfAnnos["OTR_PRI"]["sum"]
! Podemos quitar el índice de las filas
! dfAnnos.reset_index() -> Copia el valor del índice como una columna más
! dfAnnos.reset_index(drop=True) -> Si queremos no conservar el valor del índice en el dataframe
Filtrado y manipulación de dataframes
! apply() aplica una función a cada elemento del dataframe
! def suma1alanno(valor):
! anno = int(valor[0:4]) + 1
! mes = valor[5:7]
! return str(anno) + "M" + str(mes)
! datosElectricos["Grupo"].apply(suma1alanno)
! Si la función ya está definida, puedo hacer cosas como esta
! datosElectricos["Grupo"].apply("El mes es {}".format)
! datosElectricos["Grupo"].apply(len)
! datosElectricos["OTR_PRI"].apply(int)
! …
! Sólo puedo poner funciones que no necesiten aceptar dos parámetros, sólo 1
Filtrado y manipulación de dataframes
! apply() aplica una función a cada elemento del dataframe
! def suma1alanno(valor):
! anno = int(valor[0:4]) + 1
! mes = valor[5:7]
! return str(anno) + "M" + str(mes)
! datosElectricos["Grupo"].apply(suma1alanno)
! Si la función ya está definida, puedo hacer cosas como esta
! datosElectricos["Grupo"].apply("El mes es {}".format)
! datosElectricos["Grupo"].apply(len)
! datosElectricos["OTR_PRI"].apply(int)
! …
! Sólo puedo poner funciones que no necesiten aceptar dos parámetros, sólo 1
Filtrado y manipulación de dataframes
! Una función lambda es una función que está definida en una única línea y se define tal cual se usa
! datosElectricos["Grupo"].apply(lambda x: "El mes es " + x)
! En este caso se ejecuta cada línea, cada valor de “Grupo” pasa a tomar el valor de x y se ejecuta la función que
hay tras los dos puntos
! Es una forma de ejecutar funciones de múltiples parámetros
! datosElectricos["OTR_PRI"].apply(lambda x: round(x, 2))
! Esto equivale a:
! def redondea(x):
! return round(x, 2)
! datosElectricos["OTR_PRI"].apply(redondea)
Filtrado y manipulación de dataframes
! También puedo crear una tabla pivotantes
! datosElectricos["anno"] = datosElectricos["Grupo"].str[0:4]
! datosElectricos["mes"] = datosElectricos["Grupo"].str[5:7]
! [Link](index = "anno", columns="mes", values=“OTR_PRI")
!
Trabajo con fechas en Pandas
! Para convertir fechas, hay una función pd.to_datetime()
! pd.to_datetime("2009-10-24", format="%Y-%m-%d") ->Timestamp('2009-10-24 00:00:00’)
! pd.to_datetime("20091024", format="%Y%m%d") ->Timestamp('2009-10-24 00:00:00’)
! pd.to_datetime("2009-10", format="%Y-%m") ->Timestamp('2009-10-01 00:00:00’)
! pd.to_datetime("09-10", format="%y-%m") ->Timestamp('2009-10-01 00:00:00’)
! pd.to_datetime("2009/10/25 14:02:57", format="%y/%m/%d %H:%M:%S") ->Timestamp('2009-10-25 14:02:57’)
! También podemos indicar si es PM ó AM con %p
! pd.to_datetime("21/10/25 9:35:00PM", format="%y/%m/%d %I:%M:%S%p") ->Timestamp('2021-10-25 21:35:00’)
! Para convertir toda una columna
! datosElectricos["Fecha"] = pd.to_datetime(datosElectricos["Grupo"], format="%YM%m")
! Aquí están los distintos porcentajes
! [Link]
Trabajo con fechas en Pandas
! A partir de ahí podemos obtener con .dt acceso a funciones de fecha/hora
! datosElectricos["Fecha"].[Link]
! datosElectricos["Fecha"].[Link]
! datosElectricos["Fecha"].[Link]
! datosElectricos["Fecha"].[Link]
! También hay minute/second/milesecond/nanosecond
! datosElectricos["Fecha"].[Link] -> Devuelve una fecha de Python
! datosElectricos["Fecha"].dt.day_of_year()
! datosElectricos["Fecha"].dt.day_of_week()
! datosElectricos["Fecha"].dt.day_name()
! [Link]
Trabajo con fechas en Pandas
! Puedo generar una lista de fechas con date_range()
! pd.date_range(start="2018-10-25", end="2020-10-02")
! pd.date_range(start="2018-10-25", end="2020-10-02", freq="5h")
! [Link]
! pd.date_range(start="2018-10-25", end="2020-10-02").tz_localize("Europe/Madrid")
Unión de dataframes
! [Link](dataFrame2, how="inner",
left_on=col1, right_on=col2)
! Vamos a hacer un ejemplo no-trivial
! [Link]
1hhT4orqtmvHl3csMQBJr9dyk-Xy1xRVM/
view?usp=sharing
Unión de dataframes
! Vamos a leer el fichero
! smRaw = pd.read_csv(“Power-Networks-LCL-June2015(withAcornGps)[Link]")
! Cuidado, que datos nulos (no vienen como None, sino con el texto “Null”)
! ¿Cuántas tarifas hay?
! Hay datos de 2 tarifas distintas: “Std” (Tarifa normal) y “ToU” (Tarifa Nocturna)
! Quitamos las 2 cosas a la vez:
! smRaw = smRaw[(smRaw['KWH/hh (per half hour) '] != "Null") & (smRaw['stdorToU'] == “Std")]
! El campo KWH no está como número. ¿Por qué? Porque contenía valores “Null”. Una vez quitados, ya podemos
convertirlo a número
! smRaw['KWH/hh (per half hour) '] = smRaw['KWH/hh (per half hour) '].astype(float)
Unión de dataframes
! 1ª Tarea: Los datos vienen desglosados por clientes y medias horas, vamos a intentar dar una predicción por hora
(en vez de por 1/2 hora).
! La fecha/hora acaba en 00:00 ó en 00:30, habría que pasarla a 00:00 y luego sumar los consumos de todos
los hogares en cada hora
! Dos aproximaciones:
! 1ª aproximación:
! Convertir la fecha/hora de texto a fecha
! Manipular la hora para convertir todo a 00:00
! 2ª aproximación:
! Quitar los últimos 5 caracteres de la cadena y agregarles “00:00”
! Vamos con la 2ª:
! smRaw['DateTime'] = smRaw['DateTime'].[Link](0, 14) + "00:00"
Unión de dataframes
! Los nombres de las columnas tienen “/“, “(”, “)”, espacios en blanco…
! O podemos hacer
! [Link] = [“LCLid”, “stdorToU”, “DateTime”, “Consumo”, “Acorn”, “AcornG”]
! Ó
! [Link] = [Link]().[Link]().[Link](' ', '_').[Link]('(', '').[Link](')',
'').[Link]('/', '')
Unión de dataframes
! Vamos a agrupar datos por fecha y hora que me saque cuánto contadores dan lectura a esa fecha/hora y cuál es la suma de sus consumos para
únicamente el ACORN-A
! soloACORNA = smRaw[smRaw['acorn'] == ‘ACORN-A’]
! soloHoraYConsumoA = [['datetime', 'kwhhh_per_half_hour']]
! acornA = [Link](‘datetime').agg(['sum', ‘count’]).sort_values(by=['datetime'])
! Vamos a cambiarles el nombre a las columnas
! [Link] = ['kwhhh_per_half_hour', ‘count’]
! Este dataframe es un poco especial, tiene como nombre de filas la fecha/hora. Para pasarlo como una columna normal hay que hacer:
! i) Copiamos el índice en una columna
! acornA['datetime'] = [Link]
! ii) Quitamos el índice
! acornA = acornA.reset_index(drop=True)
! (También podríamos haber quitado el indice sin (drop=True) y así se incorpora como columna)
Unión de dataframes
! Ahora tenemos otro dataset del tiempo meteorológico por horas
! weather = pd.read_csv(“weather_hourly_darksky.csv”)
! Vamos a unir ambos conjuntos con un join quedándonos únicamente con los 14 primeros caracteres de la fecha
(es decir, le quitamos los minutos y los segundos)
! acornAWeather = [Link](weather, how='inner', left_on=acornA['datetime'].[Link](0, 14),
right_on=weather['time'].[Link](0, 14))
! how indica de cuántas formas hacer el join. Con inner los datos tienen que estar en ambos datasets
@AvanteFormación
@AvanteTIC
Avante
Avante Formación