2015-12-30 134 views
7

回從數字和標稱數據的數據幀:最優雅的方式來獲得pandas.df_dummies

>>> from pandas import pd 
>>> d = {'m': {0: 'M1', 1: 'M2', 2: 'M7', 3: 'M1', 4: 'M2', 5: 'M1'}, 
     'qj': {0: 'q23', 1: 'q4', 2: 'q9', 3: 'q23', 4: 'q23', 5: 'q9'}, 
     'Budget': {0: 39, 1: 15, 2: 13, 3: 53, 4: 82, 5: 70}} 
>>> df = pd.DataFrame.from_dict(d) 
>>> df 
    Budget m qj 
0  39 M1 q23 
1  15 M2 q4 
2  13 M7 q9 
3  53 M1 q23 
4  82 M2 q23 
5  70 M1 q9 

get_dummies轉換分類變量爲啞/指示變量:

>>> df_dummies = pd.get_dummies(df) 
>>> df_dummies 
    Budget m_M1 m_M2 m_M7 qj_q23 qj_q4 qj_q9 
0  39  1  0  0  1  0  0 
1  15  0  1  0  0  1  0 
2  13  0  0  1  0  0  1 
3  53  1  0  0  1  0  0 
4  82  0  1  0  1  0  0 
5  70  1  0  0  0  0  1 

什麼的最優雅back_from_dummies如何從df_dummies找回df?

>>> (back_from_dummies(df_dummies) == df).all() 
Budget True 
m   True 
qj  True 
dtype: bool 
+0

回到df?不確定你的意思。 –

+0

我只是指定回覆/回覆 – user3313834

+0

謝謝。只是想確定。 –

回答

1

idxmax會很容易做到這一點。

from itertools import groupby 

def back_from_dummies(df): 
    result_series = {} 

    # Find dummy columns and build pairs (category, category_value) 
    dummmy_tuples = [(col.split("_")[0],col) for col in df.columns if "_" in col] 

    # Find non-dummy columns that do not have a _ 
    non_dummy_cols = [col for col in df.columns if "_" not in col] 

    # For each category column group use idxmax to find the value. 
    for dummy, cols in groupby(dummmy_tuples, lambda item: item[0]): 

     #Select columns for each category 
     dummy_df = df[[col[1] for col in cols]] 

     # Find max value among columns 
     max_columns = dummy_df.idxmax(axis=1) 

     # Remove category_ prefix 
     result_series[dummy] = max_columns.apply(lambda item: item.split("_")[1]) 

    # Copy non-dummy columns over. 
    for col in non_dummy_cols: 
     result_series[col] = df[col] 

    # Return dataframe of the resulting series 
    return pd.DataFrame(result_series) 

(back_from_dummies(df_dummies) == df).all() 
1

首先,單獨列:

In [11]: from collections import defaultdict 
     pos = defaultdict(list) 
     vals = defaultdict(list) 

In [12]: for i, c in enumerate(df_dummies.columns): 
      if "_" in c: 
       k, v = c.split("_", 1) 
       pos[k].append(i) 
       vals[k].append(v) 
      else: 
       pos["_"].append(i) 

In [13]: pos 
Out[13]: defaultdict(list, {'_': [0], 'm': [1, 2, 3], 'qj': [4, 5, 6]}) 

In [14]: vals 
Out[14]: defaultdict(list, {'m': ['M1', 'M2', 'M7'], 'qj': ['q23', 'q4', 'q9']}) 

這使您可以切成每個空置列不同的幀:

In [15]: df_dummies.iloc[:, pos["m"]] 
Out[15]: 
    m_M1 m_M2 m_M7 
0  1  0  0 
1  0  1  0 
2  0  0  1 
3  1  0  0 
4  0  1  0 
5  1  0  0 

現在我們可以使用numpy的的argmax:

In [16]: np.argmax(df_dummies.iloc[:, pos["m"]].values, axis=1) 
Out[16]: array([0, 1, 2, 0, 1, 0]) 

*注意:鍋DAS idxmax返回標籤,我們想要的位置,這樣我們就可以使用Categoricals *

In [17]: pd.Categorical.from_codes(np.argmax(df_dummies.iloc[:, pos["m"]].values, axis=1), vals["m"]) 
Out[17]: 
[M1, M2, M7, M1, M2, M1] 
Categories (3, object): [M1, M2, M7] 

現在我們可以把所有這些組合起來。

In [21]: df = pd.DataFrame({k: pd.Categorical.from_codes(np.argmax(df_dummies.iloc[:, pos[k]].values, axis=1), vals[k]) for k in vals}) 

In [22]: df 
Out[22]: 
    m qj 
0 M1 q23 
1 M2 q4 
2 M7 q9 
3 M1 q23 
4 M2 q23 
5 M1 q9 

,並把後面的非空置列:

In [23]: df[df_dummies.columns[pos["_"]]] = df_dummies.iloc[:, pos["_"]] 

In [24]: df 
Out[24]: 
    m qj Budget 
0 M1 q23  39 
1 M2 q4  15 
2 M7 q9  13 
3 M1 q23  53 
4 M2 q23  82 
5 M1 q9  70 

作爲一個功能:

def reverse_dummy(df_dummies): 
    pos = defaultdict(list) 
    vals = defaultdict(list) 

    for i, c in enumerate(df_dummies.columns): 
     if "_" in c: 
      k, v = c.split("_", 1) 
      pos[k].append(i) 
      vals[k].append(v) 
     else: 
      pos["_"].append(i) 

    df = pd.DataFrame({k: pd.Categorical.from_codes(
           np.argmax(df_dummies.iloc[:, pos[k]].values, axis=1), 
           vals[k]) 
         for k in vals}) 

    df[df_dummies.columns[pos["_"]]] = df_dummies.iloc[:, pos["_"]] 
    return df 

In [31]: reverse_dummy(df_dummies) 
Out[31]: 
    m qj Budget 
0 M1 q23  39 
1 M2 q4  15 
2 M7 q9  13 
3 M1 q23  53 
4 M2 q23  82 
5 M1 q9  70 
0

與@David類似,我發現idxmax將爲您完成大部分工作。我認爲,當您嘗試將列轉換回來時,沒有萬無一失的方法來保證您沒有問題,但是,因爲在某些情況下,識別哪些列是虛擬的,哪些不是。我發現這可以通過使用不太可能偶然出現在數據中的分隔符大大緩解。 _經常用於有多個單詞的列名,所以我用__(雙下劃線)作爲分隔符;我從來沒有在野外列名中遇到過這種情況。

另外,請注意,pd.get_dummies將移動所有虛擬列到最後。這意味着您不一定會退回列的原始順序。

這是我的方法的一個例子。您可以將虛擬列識別爲其中包含sep的虛擬列。我們得到了使用df.filter的虛擬列組,這使我們能夠使用正則表達式匹配列名稱(只是sep工作前名稱的一部分;還有其他方法可以完成此部分)。

rename部分剝離列名稱的開頭(例如,m__),這樣剩下的部分就是價值。然後idxmax提取其中包含1的列名稱。這給了我們一個原始列撤消pd.get_dummies的數據框;我們將每個列上的反向pd.get_dummies的數據幀連同other_cols連接在一起 - 那些未被「僞造」的列。

In [1]: import pandas as pd 

In [2]: df = pd.DataFrame.from_dict({'m': {0: 'M1', 1: 'M2', 2: 'M7', 3: 'M1', 4: 'M2', 5: 'M1'}, 
    ...:   'qj': {0: 'q23', 1: 'q4', 2: 'q9', 3: 'q23', 4: 'q23', 5: 'q9'}, 
    ...:   'Budget': {0: 39, 1: 15, 2: 13, 3: 53, 4: 82, 5: 70}}) 

In [3]: df 
Out[3]: 
    Budget m qj 
0  39 M1 q23 
1  15 M2 q4 
2  13 M7 q9 
3  53 M1 q23 
4  82 M2 q23 
5  70 M1 q9 

In [4]: sep = '__' 

In [5]: dummies = pd.get_dummies(df, prefix_sep=sep) 

In [6]: dummies 
Out[6]: 
    Budget m__M1 m__M2 m__M7 qj__q23 qj__q4 qj__q9 
0  39  1  0  0  1  0  0 
1  15  0  1  0  0  1  0 
2  13  0  0  1  0  0  1 
3  53  1  0  0  1  0  0 
4  82  0  1  0  1  0  0 
5  70  1  0  0  0  0  1 

In [7]: dfs = [] 
    ...: 
    ...: dummy_cols = list(set(col.split(sep)[0] for col in dummies.columns if sep in col)) 
    ...: other_cols = [col for col in dummies.columns if sep not in col] 
    ...: 
    ...: for col in dummy_cols: 
    ...:  dfs.append(dummies.filter(regex=col).rename(columns=lambda name: name.split(sep)[1]).idxmax(axis=1)) 
    ...: 
    ...: df = pd.concat(dfs + [dummies[other_cols]], axis=1) 
    ...: df.columns = dummy_cols + other_cols 
    ...: df 
    ...: 
Out[7]: 
    qj m Budget 
0 q23 M1  39 
1 q4 M2  15 
2 q9 M7  13 
3 q23 M1  53 
4 q23 M2  82 
5 q9 M1  70