Resumen de habilidades prácticas de aceleración de procesamiento de datos de Pandas esenciales de ciencia de datos

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?

inserte la descripción de la imagen aquí
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.

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).
inserte la descripción de la imagen aquí

Optimización de datos de fecha y hora

inserte la descripción de la imagen aquí

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.
inserte la descripción de la imagen aquí

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()

Supongo que te gusta

Origin blog.csdn.net/qq_20288327/article/details/124244006
Recomendado
Clasificación