2015-09-08 5 views
32

Sto cercando di trasformare una cella panda contenente un elenco in righe per ciascuno di quei valori.Come esplodere un elenco all'interno di una cella Dataframe in righe separate

Quindi, prendete questo:

enter image description here

Se mi piacerebbe per spacchettare e impilare i valori nella 'colonna di nearest_neighbors" in modo che ogni valore sarebbe una riga all'interno di ogni 'indice avversario', come potrei meglio andare su questo? ci sono i panda metodi che sono pensati per operazioni di questo tipo? solo che non sono a conoscenza.

Grazie in anticipo, ragazzi.

+0

Potresti dare un esempio del tuo output desiderato, e wha hai provato fino ad ora? È più semplice per gli altri aiutarvi se fornite alcuni dati di esempio che possono essere tagliati e incollati. – dagrha

+0

Puoi usare 'pd.DataFrame (df.nearest_neighbors.values.tolist())' per decomprimere questa colonna e poi 'pd.merge' per incollarla con le altre. – hellpanderrr

+0

@helpanderr non penso 'values.tolist()' fa qualcosa qui; la colonna è già una lista – maxymoo

risposta

28

Nel codice riportato di seguito, per prima cosa ho ripristinato l'indice per semplificare l'iterazione della riga.

Creo un elenco di elenchi in cui ogni elemento dell'elenco esterno è una riga del DataFrame di destinazione e ogni elemento dell'elenco interno è una delle colonne. Questo elenco annidato verrà infine concatenato per creare il DataFrame desiderato.

Io uso una funzione lambda unitamente elenco iterazione per creare una riga per ogni elemento della nearest_neighbors abbinato al relativo name e opponent.

Infine, creo un nuovo DataFrame da questo elenco (utilizzando i nomi delle colonne originali e impostando l'indice su name e 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 

EDIT GIUGNO 2017

Un metodo alternativo è il seguente:

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

credo che questo davvero una buona domanda, in Hive useresti EXPLODE, penso che sia necessario fare in modo che Pandas includa questa funzionalità per impostazione predefinita. Si potrebbe esplodere la vostra colonna di lista come questa:

import numpy as np 

df = pd.DataFrame({'listcol':[[1,2,3],[4,5,6]]}) 
X = pd.concat([pd.DataFrame(v, index=np.repeat(k,len(v))) 
      for k,v in df.listcol.to_dict().items()])  

quindi è possibile utilizzare pd.merge a partecipare a questo nuovo al vostro dataframe originale come @helpanderr suggerito nel commento alla tua domanda iniziale.

7

Nicer con alternativa applicano (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) 
+0

Questo espande colonne non righe. – Oleg

3

simili Funzionalità EXPLODE di 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 
+1

Quando lo eseguo, ottengo il seguente errore: 'NameError: nome globale 'copia' non è definito ' – frmsaul

6

Usa apply(pd.Series) e stack, poi reset_index e 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 

dettagli

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] 
+0

Ama l'eleganza della tua soluzione! L'hai confrontato con altri approcci per caso? – rpyzh

0

Ecco un potenziale di ottimizzazione per dataframes più grandi. Questo si esegue più velocemente quando ci sono diversi valori uguali nel campo "exploding". (Più grande è il dataframe viene confrontato con il conteggio del valore univoco nel campo, migliore sarà il codice che eseguirà.)

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 
2

Procedimento veloce ho trovato finora sta estendendo la dataframe con .iloc e assegnando indietro il appiattita colonna di destinazione.

Dato il solito ingresso (replicato un po '):

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] 
... 

Dati i seguenti alternative suggerite:

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 = [c for c in 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)) 

trovo che extend_iloc() è la più veloce:

%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) 
+0

bella valutazione – javadba

Problemi correlati