La eficiencia del procesamiento de datos de Pandas sigue siendo muy buena. En comparación con los conjuntos de datos a gran escala, siempre que se domine el método correcto, puede ahorrar mucho tiempo en el procesamiento de datos.
Pandas se basa en estructuras de matriz NumPy, y muchas operaciones se realizan en C, ya sea a través de NumPy o a través de la propia biblioteca de módulos de extensión de Python de Pandas escritos en Cython y compilados en C. En teoría, la velocidad de procesamiento debería ser muy rápida.
Entonces, ¿por qué dos personas procesan los mismos datos, el tiempo de procesamiento será muy diferente cuando el equipo es el mismo?
Para ser claros, esta no es una guía sobre cómo sobreoptimizar el código de Pandas. Pandas ha sido diseñado para funcionar rápido si se usa correctamente. También hay una gran diferencia entre optimizar y escribir código limpio.
Aquí hay una guía para usar Pandas de la manera Pythonic para aprovechar sus funciones integradas potentes y fáciles de usar.
Directorio de artículos
- preparación de datos
- Optimización de datos de fecha y hora
- Bucle simple de datos
- Bucle los métodos .itertuples() y .iterrows()
- método .apply()
- Selección de datos .isin()
- Clasificación de datos .cut()
- Procesamiento del método numpy
- Comparación de eficiencia de procesamiento
- HDFStore evita el reprocesamiento
preparación de datos
El objetivo de este ejemplo es aplicar una tarifa de energía según el tiempo de uso para calcular el costo total del consumo de energía durante un año. Es decir, en diferentes momentos del día, el precio de la electricidad será diferente, por lo que la tarea es multiplicar la cantidad de electricidad consumida por hora por el precio correcto para consumir esa hora.
Lea los datos de un archivo CSV con dos columnas , una para la fecha más la hora y otra para la electricidad consumida en kilovatios-hora (kWh).
Optimización de datos de fecha y hora
import pandas as pd
df = pd.read_csv('数据科学必备Pandas实操数据处理加速技巧汇总/demand_profile.csv')
df.head()
date_time energy_kwh
0 1/1/13 0:00 0.586
1 1/1/13 1:00 0.580
2 1/1/13 2:00 0.572
3 1/1/13 3:00 0.596
4 1/1/13 4:00 0.592
A primera vista, esto se ve bien, pero hay un pequeño problema. Pandas y NumPy tienen un concepto de dtypes (tipos de datos). Si no se especifican argumentos, date_time toma el tipo de objeto .
df.dtypes
date_time object
energy_kwh float64
dtype: object
type(df.iat[0, 0])
str
object no es solo un contenedor para str, sino cualquier columna que no encaje exactamente en un tipo de datos. El manejo de fechas como cadenas sería laborioso e ineficiente (lo que también conduciría a ineficiencias en la memoria). Para procesar datos de series temporales, la columna date_time debe formatearse como una matriz de objetos de fecha y hora (Timestamp).
df['date_time'] = pd.to_datetime(df['date_time'])
df['date_time'].dtype
datetime64[ns]
Ahora hay un DataFrame llamado df con dos columnas y un índice numérico para referirse a la fila.
df.head()
date_time energy_kwh
0 2013-01-01 00:00:00 0.586
1 2013-01-01 01:00:00 0.580
2 2013-01-01 02:00:00 0.572
3 2013-01-01 03:00:00 0.596
4 2013-01-01 04:00:00 0.592
Use el decorador de temporizador %%time que viene con Jupyter para realizar pruebas.
def convert(df, column_name):
return pd.to_datetime(df[column_name])
%%time
df['date_time'] = convert(df, 'date_time')
Wall time: 663 ms
def convert_with_format(df, column_name):
return pd.to_datetime(df[column_name],format='%d/%m/%y %H:%M')
%%time
df['date_time'] = convert(df, 'date_time')
Wall time: 1.99 ms
La eficiencia de procesamiento se incrementa en casi 350 veces. En el caso de procesar datos a gran escala, el tiempo para procesar datos se ampliará infinitamente.
Bucle simple de datos
Ahora que los formatos de fecha y hora están procesados, es hora de empezar a calcular tu factura de la luz. Los costos varían según la hora, por lo que el factor de costo debe aplicarse condicionalmente a cada hora del día.
En este ejemplo, el costo del tiempo de uso se definirá en tres partes.
data_type = {
# 高峰
"Peak":{
"Cents per kWh":28,"Time Range":"17:00 to 24:00"},
# 正常时段
"Shoulder":{
"Cents per kWh":20,"Time Range":"7:00 to 17:00"},
# 非高峰
"Off-Peak":{
"Cents per kWh":12,"Time Range":"0:00 to 7:00"},
}
Si el precio es de 28 céntimos por kWh por hora del día.
df['cost_cents'] = df['energy_kwh'] * 28
date_time energy_kwh cost_cents
0 2013-01-01 00:00:00 0.586 16.408
1 2013-01-01 01:00:00 0.580 16.240
2 2013-01-01 02:00:00 0.572 16.016
3 2013-01-01 03:00:00 0.596 16.688
4 2013-01-01 04:00:00 0.592 16.576
...
Pero el costo depende de la hora del día. Aquí es donde verás que mucha gente usa Pandas de formas inesperadas, escribiendo un ciclo para hacer cálculos condicionales.
def apply_tariff(kwh, hour):
"""计算给定小时的电费"""
if 0 <= hour < 7:
rate = 12
elif 7 <= hour < 17:
rate = 20
elif 17 <= hour < 24:
rate = 28
else:
raise ValueError(f'无效时间: {
hour}')
return rate * kwh
def apply_tariff(kwh, hour):
"""计算给定小时的电费"""
if 0 <= hour < 7:
rate = 12
elif 7 <= hour < 17:
rate = 20
elif 17 <= hour < 24:
rate = 28
else:
raise ValueError(f'无效时间: {
hour}')
return rate * kwh
def apply_tariff_loop(df):
energy_cost_list = []
for i in range(len(df)):
# 循环数据直接修改df
energy_used = df.iloc[i]['energy_kwh']
hour = df.iloc[i]['date_time'].hour
energy_cost = apply_tariff(energy_used, hour)
energy_cost_list.append(energy_cost)
df['cost_cents'] = energy_cost_list
Wall time: 2.59 s
Bucle los métodos .itertuples() y .iterrows()
En realidad, Pandas hace que la sintaxis sea potencialmente redundante para i in range(len(df)) mediante la introducción de los métodos DataFrame.itertuples() y DataFrame.iterrows() , que son métodos generadores que producen una fila a la vez.
- .itertuples() genera una tupla con nombre para cada fila, con el valor del índice de fila como el primer elemento de la tupla. Las tuplas de nombres son estructuras de datos del módulo de colecciones de Python que se comportan como tuplas de Python, pero tienen campos accesibles a través de la búsqueda de atributos.
- .iterrows() genera (índice, Serie) pares (tuplas) para cada fila en el DataFrame.
def apply_tariff_iterrows(df):
energy_cost_list = []
for index, row in df.iterrows():
energy_used = row['energy_kwh']
hour = row['date_time'].hour
energy_cost = apply_tariff(energy_used, hour)
energy_cost_list.append(energy_cost)
df['cost_cents'] = energy_cost_list
%%time
apply_tariff_iterrows(df)
Wall time: 808 ms
La velocidad aumenta hasta 3 veces.
método .apply()
Esta operación se puede mejorar aún más utilizando el método .apply() . El método .apply() de Pandas toma funciones (invocables) y las aplica a lo largo de los ejes de DataFrame (todas las filas o todas las columnas).
La función lambda pasa dos columnas de datos a apply_tariff() .
def apply_tariff_withapply(df):
df['cost_cents'] = df.apply(
lambda row: apply_tariff(
kwh=row['energy_kwh'],
hour=row['date_time'].hour),
axis=1)
%%time
apply_tariff_withapply(df)
Wall time: 181 ms
La ventaja de sintaxis de .apply() es obvia, el código es conciso, fácil de leer y claro. El tiempo necesario en este caso es aproximadamente 1/4 del tiempo del método .iterrows() .
Selección de datos .isin()
Pero, ¿cómo aplicar el cómputo condicional como una operación vectorizada en Pandas? Un truco consiste en seleccionar y agrupar partes de un DataFrame en función de una condición y luego aplicar una operación de vectorización a cada grupo seleccionado.
Use el método .isin() de Pandas para seleccionar filas y luego aplicarlas en una operación vectorizada. Antes de hacer esto, sería más conveniente si la columna date_time se estableciera como índice del DataFrame.
df.set_index('date_time', inplace=True)
def apply_tariff_isin(df):
peak_hours = df.index.hour.isin(range(17, 24))
shoulder_hours = df.index.hour.isin(range(7, 17))
off_peak_hours = df.index.hour.isin(range(0, 7))
df.loc[peak_hours, 'cost_cents'] = df.loc[peak_hours, 'energy_kwh'] * 28
df.loc[shoulder_hours,'cost_cents'] = df.loc[shoulder_hours, 'energy_kwh'] * 20
df.loc[off_peak_hours,'cost_cents'] = df.loc[off_peak_hours, 'energy_kwh'] * 12
%%time
apply_tariff_isin(df)
Wall time: 53.5 ms
donde el método de proceso completo devuelve una lista booleana.
[False, False, False, ..., True, True, True]
Clasificación de datos .cut()
Configurar una lista de divisiones de tiempo y una fórmula de función para ese cálculo facilita la operación, pero el código es un poco difícil de leer para un novato.
def apply_tariff_cut(df):
cents_per_kwh = pd.cut(x=df.index.hour,
bins=[0, 7, 17, 24],
include_lowest=True,
labels=[12, 20, 28]).astype(int)
df['cost_cents'] = cents_per_kwh * df['energy_kwh']
%%time
apply_tariff_cut(df)
Wall time: 2.99 ms
Procesamiento del método numpy
Pandas Series y DataFrames están diseñados sobre la biblioteca NumPy. Esto proporciona una mayor flexibilidad computacional, ya que Pandas funciona a la perfección con las matrices y operaciones de NumPy.
Usa la función digitalizar() de NumPy. Es similar al cut() de Pandas en el sentido de que los datos se agruparán, pero esta vez estarán representados por una serie de índices que indican a qué contenedor pertenece cada hora. Estos índices se aplican luego a la matriz de precios.
import numpy as np
def apply_tariff_digitize(df):
prices = np.array([12, 20, 28])
bins = np.digitize(df.index.hour.values, bins=[7, 17, 24])
df['cost_cents'] = prices[bins] * df['energy_kwh'].values
%%time
apply_tariff_digitize(df)
Wall time: 1.99 ms
Comparación de eficiencia de procesamiento
Compare la eficiencia de los diferentes métodos de procesamiento anteriores.
Características | Tiempo de ejecución (segundos) |
---|---|
aplicar_tarifa_bucle() | 2,59 s |
apply_tariff_iterows() | 808ms |
aplicar_tarifa_conaplicar() | 181ms |
apply_tariff_isin() | 53,5 ms |
aplicar_corte_de_tarifa() | 2,99 ms |
aplicar_tarifa_digitalizar() | 1,99 ms |
HDFStore evita el reprocesamiento
A menudo, cuando se construyen modelos de datos complejos, es conveniente realizar un preprocesamiento de los datos. Si tiene 10 años de datos de uso de electricidad con frecuencia por minuto, la simple conversión de la fecha y la hora a fecha y hora puede demorar 20 minutos, incluso si se especifica el parámetro de formato. Solo necesita hacer esto una vez en lugar de requerir una prueba o análisis cada vez que ejecuta el modelo.
Una cosa muy útil que se puede hacer aquí es preprocesar y luego almacenar los datos en una forma procesada para que se puedan usar cuando sea necesario. Pero, ¿cómo puedo almacenar los datos en el formato correcto sin tener que volver a procesarlos? Si tuviera que guardar en CSV, simplemente perdería su objeto de fecha y hora y tendría que volver a procesarlo cuando vuelva a acceder a él.
Pandas tiene una solución integrada que utiliza HDF5, un formato de almacenamiento de alto rendimiento diseñado para almacenar matrices de datos tabulares. La clase HDFStore de Pandas permite que un DataFrame se almacene en un archivo HDF5 para que se pueda acceder a él de manera eficiente mientras se conservan los tipos de columna y otros metadatos. dict es una clase similar a un diccionario, por lo que se puede leer y escribir como objetos de Python.
Almacene el DataFrame df de consumo de energía preprocesado en un archivo HDF5.
data_store = pd.HDFStore('processed_data.h5')
# 将 DataFrame 放入对象中,将键设置为 preprocessed_df
data_store['preprocessed_df'] = df
data_store.close()
Un método para acceder a los datos de un archivo HDF5, conservando el tipo de datos.
data_store = pd.HDFStore('processed_data.h5')
preprocessed_df = data_store['preprocessed_df']
data_store.close()