2015-09-08 7 views
32

リストを含むpandasセルを、それぞれの値の行に変換することを検討しています。Dataframeセル内のリストを別々の行に分解する方法

ので、これを取る:

enter image description here

を私は相手のインデックス「は、それぞれ値がそれぞれ内の行になるようにnearest_neighbors」列 'に展開した値をスタックしたい場合は、どのように私は最高の?私は認識していないんだ?このついて行くこのような操作のために意図されている方法はありパンダはありますでしょう。

事前のおかげで、みんな。

+0

あなたはあなたの希望する出力の例を与えることができますか?あなたはこれまでに試したことがありますか?カット&ペーストすることができるサンプルデータを提供すると、他の人が手助けするのが最も簡単です。 – dagrha

+0

'pd.DataFrame(df.nearest_neighbors.values.tolist())'を使ってこの列を展開し、それを他の列と貼り付ける 'pd.merge'を使うことができます。 – hellpanderrr

+0

@helpanderr 'values.tolist()'はここでは何もしません。列は既にリストになっています – maxymoo

答えて

28

以下のコードでは、最初にインデックスをリセットして、行の反復を簡単にしました。

外部リストの各要素がターゲットDataFrameの行で、内部リストの各要素が列の1つであるリストのリストを作成します。このネストされたリストは最終的に連結され、目的のDataFrameを作成します。

Iは関連nameopponentとペアnearest_neighborsの各要素の行を作成するリスト反復とともにlambda関数を使用します。

最後に、このリストから元の列名を使用してインデックスをnameopponentに戻して新しいDataFrameを作成します。

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 2017年6月

次のように別の方法である:適用(pd.Series)と

>>> (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

を私は、これは本当に良い質問だと思いますあなたはハイブを使用するでしょうEXPLODEでは、パンダがデフォルトでこの機能を組み込むべきケースがあると私は思う。あなたはこのようなあなたのリストの列を爆発可能性:

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()])  

@helpanderrがあなたの元の質問にコメントで示唆されているように、あなたはあなたの元データフレームに、このバックに参加するpd.mergeを使用することができます。

7

よりよい代替ソリューション:

と同様
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

この列は列を展開しません。 – Oleg

3

ハイブのEXPLODE機能:

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

これを実行すると、次のエラーが表示されます。 'NameError:グローバル名 'copy'が定義されていません。 – frmsaul

6

使用apply(pd.Series)stack、その後、reset_indexto_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 

詳細は

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

あなたのソリューションのエレガンスが大好きです!万が一、他のアプローチとベンチマークしましたか? – rpyzh

0

ここでは、より大きなデータフレームのための潜在的な最適化です。これは、「爆発」フィールドに複数の等しい値がある場合、より速く実行されます。 (データフレームが大きいほどフィールド内のユニークな値の数と比較されるほど、このコードが優れています。)

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

私はこれまで.ilocでデータフレームを延ばし、ターゲット列を平坦バック割り当てるさ見出さ最速方法。

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

を考えると、次の修正候補:

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)) 

私はextend_iloc()最速であることを見つける:(ビット複製)通常の入力が与えられ

%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

すばらしい評価 – javadba

関連する問題