Tengo el siguiente marco de datos donde col2 es un diccionario con una lista de tuplas como valores. Las claves se 'agregan' y 'eliminan' constantemente en todo el marco de datos.
Entrada df
| col1 | col2 |
|---|---|
| valor1 | {'agregado': [(59, 'dep1_v2'), (60, 'dep2_v2')], 'eliminado': [(59, 'dep1_v1'), (60, 'dep2_v1')]} |
| valor 2 | {'agregado': [(61, 'dep3_v2')], 'eliminado': [(61, 'dep3_v1')]} |
Aquí hay un marco de datos de ejemplo que se puede copiar y pegar:
jsons = ["{'added': [(59, 'dep1_v2'), (60, 'dep2_v2')], 'deleted': [(59, 'dep1_v1'), (60, 'dep2_v1')]}", "{'added': [(61, 'dep3_v2')], 'deleted': [(61, 'dep3_v1')]}"] df = pd.DataFrame({"col1": ["value1", "value2"], "col2": jsons})editar
col2 proviene directamente del campo diff_parsed de la salida de pydriller
Quiero "explotar" col2 para obtener el siguiente resultado:
Salida deseada
| col1 | número | agregado | eliminado |
|---|---|---|---|
| valor1 | 59 | dep1_v2 | dep1_v1 |
| valor1 | 60 | dep2_v2 | dep2_v1 |
| valor2 | 61 | dep3_v2 | dep3_v1 |
Hasta ahora, probé lo siguiente:
df = df.join(pd.json_normalize(df.col2)) df.drop(columns=['col2'], inplace=True)El código anterior está simplificado. Primero manipulo la columna para convertirla a json adecuado. Fue en un intento de explotar primero en 'agregado' y 'eliminado' y luego tratar de jugar con el formato para obtener lo que quiero... pero la lista de tuplas no se conserva y obtengo lo siguiente:
| col1 | agregado | eliminado |
|---|---|---|
| valor1 | 59, dep1_v2, 60, dep2_v2 | 59, dep1_v1, 60, dep2_v1 |
| valor2 | 61, dep3_v1 | 61, dep3_v2 |
Gracias
Obtenga datos para agregar y eliminar (los valores en col2, se han convertido en dictados):
added, deleted = [df.col2.str[head].explode() .apply(pd.Series).set_axis(['number', head], axis = 1) for head in ('added', 'deleted')]Deshágase de la columna de números duplicados:
added = added['added']Recortar df:
df = df['col1']Concatenar:
pd.concat([df, added, deleted], axis = 1) col1 added number deleted 0 value1 dep1_v2 59 dep1_v1 0 value1 dep2_v2 60 dep2_v1 1 value 2 dep3_v2 61 dep3_v1La solución de @ddejohn debería ser más eficaz, ya que se trata de estructuras nativas de Python, antes de volcarlas en pandas.
Aquí hay una solución. Es un poco largo, pero funciona:
tmp = pd.concat([df, pd.json_normalize(df['col2'])], axis=1).drop('col2', axis=1).explode(['added', 'deleted']) new_df = pd.concat([tmp.drop(['added', 'deleted'], axis=1).reset_index(drop=True), pd.DataFrame(tmp['added'].tolist()).merge(pd.DataFrame(tmp['deleted'].tolist()), on=0).set_axis(['number', 'added', 'deleted'], axis=1)], axis=1)Producción:
>>> new_df col1 number added deleted 0 value1 59 dep1_v2 dep1_v1 1 value1 60 dep2_v2 dep2_v1 2 value2 61 dep3_v2 dep3_v1Bueno, esto ciertamente no es elegante, pero aquí hay una solución potencial que es al menos más fácil de entender y razonar:
def explode_records(df): new_records = [] def map_dict_to_row(value, col2_dict): temp = {} for number, added in col2_dict["added"]: temp[number] = {"value": value, "number": number, "added": added} for number, deleted in col2_dict["deleted"]: if number in temp: temp[number] = {**temp[number], "deleted": deleted} else: temp[number] = {"value": value, "deleted": deleted} new_records.extend(list(temp.values())) df.apply(lambda row: map_dict_to_row(row.col1, row.col2), axis=1) # assumes col2 is a dict return pd.DataFrame(new_records)Uso:
In [4]: explode_records(df) Out[4]: value number added deleted 0 value1 59 dep1_v2 dep1_v1 1 value1 60 dep2_v2 dep2_v1 2 value 2 61 dep3_v2 dep3_v1 Tenga en cuenta que obtuve el value 2 de sus datos originales. Supongo que es solo un error tipográfico, y no que también necesite la funcionalidad value x -> valuex .
No pude hacer funcionar la otra solución, por lo que no pude comparar su rendimiento con el mío.