hacer - seleccionar filas data frame python
Cómo explotar una lista dentro de una celda de Dataframe en filas separadas (10)
Estoy buscando convertir una celda de pandas que contiene una lista en filas para cada uno de esos valores.
Entonces, toma esto:
Si quisiera desempaquetar y apilar los valores en la columna de
nearest_neighbors
para que cada valor sea una fila dentro de cada índice de
opponent
, ¿cuál sería la mejor manera de hacerlo?
¿Existen métodos de pandas para operaciones como esta?
Aquí hay una optimización potencial para marcos de datos más grandes. Esto se ejecuta más rápido cuando hay varios valores iguales en el campo "explosivo". (Cuanto mayor sea el marco de datos en comparación con el recuento de valores únicos en el campo, mejor funcionará este código).
def lateral_explode(dataframe, fieldname):
temp_fieldname = fieldname + ''_made_tuple_''
dataframe[temp_fieldname] = dataframe[fieldname].apply(tuple)
list_of_dataframes = []
for values in dataframe[temp_fieldname].unique().tolist():
list_of_dataframes.append(pd.DataFrame({
temp_fieldname: [values] * len(values),
fieldname: list(values),
}))
dataframe = dataframe[list(set(dataframe.columns) - set([fieldname]))]/
.merge(pd.concat(list_of_dataframes), how=''left'', on=temp_fieldname)
del dataframe[temp_fieldname]
return dataframe
Creo que esta es una muy buena pregunta, en Hive
EXPLODE
, creo que hay que argumentar que Pandas debería incluir esta funcionalidad por defecto.
Probablemente explotaría la columna de la lista con una comprensión de generador anidada como esta:
pd.DataFrame({
"name": i[0],
"opponent": i[1],
"nearest_neighbor": neighbour
}
for i, row in df.iterrows() for neighbour in row.nearest_neighbors
).set_index(["name", "opponent"])
El método
más rápido
que encontré hasta ahora es extender el DataFrame con
.iloc
y asignar de nuevo la columna de destino
aplanada
.
Dada la entrada habitual (replicada un poco):
df = (pd.DataFrame({''name'': [''A.J. Price''] * 3,
''opponent'': [''76ers'', ''blazers'', ''bobcats''],
''nearest_neighbors'': [[''Zach LaVine'', ''Jeremy Lin'', ''Nate Robinson'', ''Isaia'']] * 3})
.set_index([''name'', ''opponent'']))
df = pd.concat([df]*10)
df
Out[3]:
nearest_neighbors
name opponent
A.J. Price 76ers [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
blazers [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
bobcats [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
76ers [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
blazers [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
...
Dadas las siguientes alternativas sugeridas:
col_target = ''nearest_neighbors''
def extend_iloc():
# Flatten columns of lists
col_flat = [item for sublist in df[col_target] for item in sublist]
# Row numbers to repeat
lens = df[col_target].apply(len)
vals = range(df.shape[0])
ilocations = np.repeat(vals, lens)
# Replicate rows and add flattened column of lists
cols = [i for i,c in enumerate(df.columns) if c != col_target]
new_df = df.iloc[ilocations, cols].copy()
new_df[col_target] = col_flat
return new_df
def melt():
return (pd.melt(df[col_target].apply(pd.Series).reset_index(),
id_vars=[''name'', ''opponent''],
value_name=col_target)
.set_index([''name'', ''opponent''])
.drop(''variable'', axis=1)
.dropna()
.sort_index())
def stack_unstack():
return (df[col_target].apply(pd.Series)
.stack()
.reset_index(level=2, drop=True)
.to_frame(col_target))
Encuentro que
extend_iloc()
es el
más rápido
:
%timeit extend_iloc()
3.11 ms ± 544 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit melt()
22.5 ms ± 1.25 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit stack_unstack()
11.5 ms ± 410 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
En el siguiente código, primero restablezco el índice para facilitar la iteración de la fila.
Creo una lista de listas donde cada elemento de la lista externa es una fila del DataFrame de destino y cada elemento de la lista interna es una de las columnas. Esta lista anidada finalmente se concatenará para crear el DataFrame deseado.
Utilizo una función
lambda
junto con la iteración de la lista para crear una fila para cada elemento de los
nearest_neighbors
emparejados con el
name
y el
opponent
relevantes.
Finalmente, creo un nuevo DataFrame a partir de esta lista (usando los nombres de columna originales y configurando el índice de nuevo a
name
y
opponent
).
df = (pd.DataFrame({''name'': [''A.J. Price''] * 3,
''opponent'': [''76ers'', ''blazers'', ''bobcats''],
''nearest_neighbors'': [[''Zach LaVine'', ''Jeremy Lin'', ''Nate Robinson'', ''Isaia'']] * 3})
.set_index([''name'', ''opponent'']))
>>> df
nearest_neighbors
name opponent
A.J. Price 76ers [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
blazers [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
bobcats [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
df.reset_index(inplace=True)
rows = []
_ = df.apply(lambda row: [rows.append([row[''name''], row[''opponent''], nn])
for nn in row.nearest_neighbors], axis=1)
df_new = pd.DataFrame(rows, columns=df.columns).set_index([''name'', ''opponent''])
>>> df_new
nearest_neighbors
name opponent
A.J. Price 76ers Zach LaVine
76ers Jeremy Lin
76ers Nate Robinson
76ers Isaia
blazers Zach LaVine
blazers Jeremy Lin
blazers Nate Robinson
blazers Isaia
bobcats Zach LaVine
bobcats Jeremy Lin
bobcats Nate Robinson
bobcats Isaia
EDITAR JUNIO 2017
Un método alternativo es el siguiente:
>>> (pd.melt(df.nearest_neighbors.apply(pd.Series).reset_index(),
id_vars=[''name'', ''opponent''],
value_name=''nearest_neighbors'')
.set_index([''name'', ''opponent''])
.drop(''variable'', axis=1)
.dropna()
.sort_index()
)
Entonces, todas estas respuestas son buenas, pero quería algo ^ realmente simple ^, así que aquí está mi contribución:
df = (pd.DataFrame({''name'': [''A.J. Price''] * 3,
''opponent'': [''76ers'', ''blazers'', ''bobcats''],
''nearest_neighbors'': [[''Zach LaVine'', ''Jeremy Lin'', ''Nate Robinson'', ''Isaia'']] * 3})
.set_index([''name'', ''opponent'']))
df.explode(''nearest_neighbors'')
Eso es todo ... solo usa esto cuando quieras una nueva serie donde las listas están ''explotadas''. Aquí hay un ejemplo donde hacemos value_counts () en opciones de tacos :)
nearest_neighbors
name opponent
A.J. Price 76ers Zach LaVine
76ers Jeremy Lin
76ers Nate Robinson
76ers Isaia
blazers Zach LaVine
blazers Jeremy Lin
blazers Nate Robinson
blazers Isaia
bobcats Zach LaVine
bobcats Jeremy Lin
bobcats Nate Robinson
bobcats Isaia
Explotar una columna tipo lista se ha
simplificado significativamente en pandas 0.25
con la adición del método
explode()
:
def explode(series):
return pd.Series([x for _list in series for x in _list])
Fuera:
In [1]: my_df = pd.DataFrame(pd.Series([[''a'',''b'',''c''],[''b'',''c''],[''c'']]), columns=[''tacos''])
In [2]: my_df.head()
Out[2]:
tacos
0 [a, b, c]
1 [b, c]
2 [c]
In [3]: explode(my_df[''tacos'']).value_counts()
Out[3]:
c 3
b 2
a 1
Extender la respuesta
.iloc
de Oleg para aplanar automáticamente todas las columnas de la lista:
def extend_iloc(df):
cols_to_flatten = [colname for colname in df.columns if
isinstance(df.iloc[0][colname], list)]
# Row numbers to repeat
lens = df[cols_to_flatten[0]].apply(len)
vals = range(df.shape[0])
ilocations = np.repeat(vals, lens)
# Replicate rows and add flattened column of lists
with_idxs = [(i, c) for (i, c) in enumerate(df.columns) if c not in cols_to_flatten]
col_idxs = list(zip(*with_idxs)[0])
new_df = df.iloc[ilocations, col_idxs].copy()
# Flatten columns of lists
for col_target in cols_to_flatten:
col_flat = [item for sublist in df[col_target] for item in sublist]
new_df[col_target] = col_flat
return new_df
Esto supone que cada columna de lista tiene la misma longitud de lista.
Similar a la funcionalidad EXPLODE de Hive:
import copy
def pandas_explode(df, column_to_explode):
"""
Similar to Hive''s EXPLODE function, take a column with iterable elements, and flatten the iterable to one element
per observation in the output table
:param df: A dataframe to explod
:type df: pandas.DataFrame
:param column_to_explode:
:type column_to_explode: str
:return: An exploded data frame
:rtype: pandas.DataFrame
"""
# Create a list of new observations
new_observations = list()
# Iterate through existing observations
for row in df.to_dict(orient=''records''):
# Take out the exploding iterable
explode_values = row[column_to_explode]
del row[column_to_explode]
# Create a new observation for every entry in the exploding iterable & add all of the other columns
for explode_value in explode_values:
# Deep copy existing observation
new_observation = copy.deepcopy(row)
# Add one (newly flattened) value from exploding iterable
new_observation[column_to_explode] = explode_value
# Add to the list of new observations
new_observations.append(new_observation)
# Create a DataFrame
return_df = pandas.DataFrame(new_observations)
# Return
return return_df
Solución alternativa más agradable con apply (pd.Series):
df = pd.DataFrame({''listcol'':[[1,2,3],[4,5,6]]})
# expand df.listcol into its own dataframe
tags = df[''listcol''].apply(pd.Series)
# rename each variable is listcol
tags = tags.rename(columns = lambda x : ''listcol_'' + str(x))
# join the tags dataframe back to the original dataframe
df = pd.concat([df[:], tags[:]], axis=1)
Use
apply(pd.Series)
y
stack
, luego
reset_index
y
to_frame
In [1803]: (df.nearest_neighbors.apply(pd.Series)
.stack()
.reset_index(level=2, drop=True)
.to_frame(''nearest_neighbors''))
Out[1803]:
nearest_neighbors
name opponent
A.J. Price 76ers Zach LaVine
76ers Jeremy Lin
76ers Nate Robinson
76ers Isaia
blazers Zach LaVine
blazers Jeremy Lin
blazers Nate Robinson
blazers Isaia
bobcats Zach LaVine
bobcats Jeremy Lin
bobcats Nate Robinson
bobcats Isaia
Detalles
In [1804]: df
Out[1804]:
nearest_neighbors
name opponent
A.J. Price 76ers [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
blazers [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
bobcats [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]