2015-12-30 16 views
7

Da un dataframe con dati numerici e nominale:Il modo più elegante per tornare da 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 convertono variabile categoriale in fittizi/variabili indicatore:

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

Qual è la cosa più elegante back_from_dummies modo per tornare da df_dummies a df?

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

Torna a df? Non sei sicuro di cosa intendi esattamente. –

+0

Ho solo specificato di tornare indietro/ripristinare – user3313834

+0

Grazie. Volevo solo essere sicuro. –

risposta

1

idxmax lo farà abbastanza facilmente.

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 primo luogo, separare le colonne:

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']}) 

Questo consente di suddividere nelle varie cornici per ogni colonna dummied:

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 

Ora possiamo usare argmax di NumPy:

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

* Nota: panoramica Das idxmax restituisce l'dell'etichetta, vogliamo la posizione in modo da poter usare categorici *

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] 

Ora siamo in grado di mettere tutto questo insieme:.

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 

e rimettere le colonne non dummied :

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 

In funzione:

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

Simile a @ Davide, trovo che idxmax farà la maggior parte del lavoro per voi. Penso che non ci sia alcun modo infallibile per garantire che non si verifichino problemi quando si sta tentando di convertire le colonne, perché, in alcuni casi, può essere difficile identificare quali colonne sono manichini e quali no. Trovo che questo può essere notevolmente alleviato utilizzando un separatore che è molto improbabile che si verifichi nei dati per caso. _ è spesso usato nei nomi di colonne che hanno più parole, quindi uso __ (doppio trattino basso) come separatore; Non ho mai incontrato questo in un nome di colonna in natura.

Inoltre, notare che pd.get_dummies sposta tutte le colonne fittizie fino alla fine. Ciò significa che non è possibile ottenere di nuovo l'ordine originale delle colonne.

Ecco un esempio del mio approccio. Puoi riconoscere le colonne fittizie come quelle con sep in esse. Otteniamo i gruppi di colonne fittizie usando df.filter che ci permetterà di abbinare i nomi delle colonne usando le espressioni regolari (solo la parte del nome prima dei sep funziona, ci sono altri modi in cui potresti fare anche questa parte).

La parte rename rimuove l'inizio dei nomi delle colonne (ad es.m__) in modo che la parte rimanente sia il valore. Quindi idxmax estrae il nome della colonna che contiene 1. Questo ci dà il dataframe da annullare pd.get_dummies su una delle colonne originali; concateniamo insieme i dataframes dall'inversione di pd.get_dummies su ciascuna colonna, insieme a other_cols - quelle colonne che non erano "dummified".

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 
Problemi correlati