馃樇 Haseando y optimizando en pandas
En primer lugar he de darle las gracias a Mario Pinto por alentarme a escribir este post, recientemente nos hemos encontrado un c贸digo "mejorable" el cu谩l ya ten铆a una base de tests. El caso era que deb铆amos de remover de nuestros datos una serie de filas usando pandas en base a un conjunto de columnas, un filtro compuesto por as铆 decirlo. El caso era que el c贸digo que ten铆amos no estaba muy vectorizado para trabajar con datos. Veamos la tabla por la que hay que filtrar:
Tabla de filtrado
| ID | year | category | | --- | ---- | -------- | | A1 | 1 | A | | B2 | 2 | B | | ... | ... | ... |
La idea aqu铆 era simplificar el c贸digo lo m谩ximo posible tanto el proceso como
la complejidad del c贸digo por lo que se nos ocurri贸 generar una nueva columna
con el valor hash siendo la
suma de todos los elementos a filtrar. Nos dimos cuenta r谩pidamente de que
pandas necesita usar su propia funci贸n de hashing, ya que usar la implementaci贸n
de hash de python requer铆a un apply
o un applymap
.
Ejemplo con apply
df['hash'] = df.apply(lambda x: hash(x['ID'] + x['year'] + x['category']), axis = 1)
Ejemplo con sin apply
df['hash'] = pd.util.hash_array( df['ID'].to_numpy() + df['year'].to_numpy() + df['category'].to_numpy() )
Usamos el .to_numpy()
para convertir el valor de la pd.Series
en un array
que contiene los 3 valores [ID, year, category]
para luego generar un n煤mero
煤nico en base a esos valores.
Si usas pandas.util.hash_pandas_object recuerda que hace un hash sobre el 铆ndice del objeto en cuesti贸n por lo que no nos sirve para comparar con un objeto con diferente 铆ndice en otro dataframe.
Ahora si ya tenemos en la tabla de filtrado la nueva columna.
Tabla de filtrado
| ID | year | category | hash | | --- | ---- | -------- | ---- | | A1 | 1 | A | 101 | | B2 | 2 | B | 110 | | ... | ... | ... | ... |
La funci贸n quedar铆a a帽adirla a los datos:
Tabla de datos
| ID | year | category | hash | more_columns... | | --- | ---- | -------- | ---- | --------------- | | A1 | 1 | A | 101 | ... | | B2 | 2 | B | 110 | ... | | ... | ... | ... | ... | ... |
Y ahora solo quedar铆a filtrar para quedarnos con aquellos hashes que no aparezcan en la tabla de filtrado:
Filtrado de los datos
are_not_on_filter_table = (~data['hash'].isin(filter_table['hash'])) filtered = data[are_not_on_filter_table].drop(columns=['hash'])
Conclusi贸n
Este es un caso que se puede resolver de muchas maneras, en este caso hemos
intentado resolverlo de una manera con pocas l铆neas de c贸digo, que no se usase
apply
en cuesti贸n de rendimiento va bastante bien y es simple de a帽adir o
quitar columnas para filtrar. Si por ejemplo hubiesen muchos valores en la tabla
de filtrado el .isin()
tendr铆a que comprobar para todas las filas de data
buscar por las N filas de la tabla de filtrado, probablemente haya
soluciones mejores como merge
.