Quiero hacer una función que haga un ETL a una lista/serie y devuelva una serie con atributos y métodos adicionales específicos para esa función. Puedo lograr esto creando una clase para extender la Serie y funciona, pero luego, cuando intento reasignar el resultado de la función con la nueva clase, los atributos y métodos de clase actualizados se eliminan. ¿Cómo puedo ampliar la serie para que tenga atributos y métodos personalizados que no se eliminen al reasignar de nuevo al marco de datos?
import pandas as pd def normalize_x(x: list, new_attribute: None): normalized = pd.Series(['normalized_'+ i if i != 4 else None for i in x]) return NormalizeX(normalized = normalized, original = x, new_attribute = new_attribute) class NormalizeX(pd.Series): def __init__(self, normalized, original, new_attribute, *args, **kwargs,): super().__init__(data = normalized, *args, **kwargs) self.original = original self.normalized = normalized self.new_attribute = new_attribute def conversion_errors(self): return [o != n for o, n in zip(pd.isnull(self.original), pd.isnull(self.normalized))] df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6], "C": ['dog', 'cat', 4]}) out = normalize_x(df.C, new_attribute = 'CoolAttribute') out ## 0 normalized_dog ## 1 normalized_cat ## 2 None ## dtype: object ## Can still use Series methods out.to_list() ## ['normalized_dog', 'normalized_cat', None] ## Can use the new methods and access attributes out.conversion_errors() ## [False, False, True] out.original ##0 dog ##1 cat ##2 4 ##Name: C, dtype: object df['new'] = normalize_x(df.C, new_attribute = 'CoolAttribute') df['new'] ## 0 normalized_dog ## 1 normalized_cat ## 2 None ## dtype: object ## Can't use the new methods or access attributes df['new'].conversion_errors() ## AttributeError: 'Series' object has no attribute 'conversion_errors' df['new'].original ## AttributeError: 'Series' object has no attribute 'original'Es demasiado difícil para mí implementar la funcionalidad deseada, por lo que solo comparto lo que encontré en mi investigación esperando que pueda ser útil para otros respondedores.
La razón por la que obtuvo esos errores de atributo es que se llevaron a cabo procesos de saneamiento en la Series que pasó al DataFrame .
id s: Puede confirmar rápidamente la diferencia entre lo que out y df['new'] se refieren mediante el siguiente código:
out = normalize_x(df.C, new_attribute = 'CoolAttribute') df['new'] = out print(id(out)) print(id(df['new'])) 1861777917792 1861770685504 puede ver out y df['new'] son diferentes entre sí debido a esta diferencia de id .
Sumerjámonos en el código fuente de pandas para ver qué sucede aquí.
DataFrame._set_item : En la definición de la clase DataFrame , el método _set_item funciona cuando intenta agregar Series a DataFrame en una columna específica.
def _set_item(self, key, value) -> None: """ Add series to DataFrame in specified column. If series is a numpy-array (not a Series/TimeSeries), it must be the same length as the DataFrames index or an error will be thrown. Series/TimeSeries will be conformed to the DataFrames index to ensure homogeneity. """ value = self._sanitize_column(value) En este método, value = self._sanitize_column(value) en la primera línea excepto en la cadena de documentación. Este método _sanitize_column en realidad destruyó la funcionalidad de la Series original. Si profundiza en este método, finalmente llegará a las siguientes líneas :
def _reindex_for_setitem(value: FrameOrSeriesUnion, index: Index) -> ArrayLike: # reindex if necessary if value.index.equals(index) or not len(index): return value._values.copy() value._values.copy() es la causa directa de la desaparición de los atributos de NormalizeX . Simplemente copia los valores de la Series dada. Por lo tanto, el método _set_item debe modificarse para proteger los atributos de NormalizeX .
Debe anular la clase DataFrame para configurar su NormalizeX en una columna específica manteniendo sus atributos.