Reconstruye una variable categórica a partir de maniquíes en pandas

pd.get_dummies permite convertir una variable categórica en variables ficticias. Además del hecho de que es trivial reconstruir la variable categórica, ¿hay una forma preferida / rápida de hacerlo?

 In [46]: s = Series(list('aaabbbccddefgh')).astype('category') In [47]: s Out[47]: 0 a 1 a 2 a 3 b 4 b 5 b 6 c 7 c 8 d 9 d 10 e 11 f 12 g 13 h dtype: category Categories (8, object): [a < b < c < d < e < f < g < h] In [48]: df = pd.get_dummies(s) In [49]: df Out[49]: abcdefgh 0 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 2 1 0 0 0 0 0 0 0 3 0 1 0 0 0 0 0 0 4 0 1 0 0 0 0 0 0 5 0 1 0 0 0 0 0 0 6 0 0 1 0 0 0 0 0 7 0 0 1 0 0 0 0 0 8 0 0 0 1 0 0 0 0 9 0 0 0 1 0 0 0 0 10 0 0 0 0 1 0 0 0 11 0 0 0 0 0 1 0 0 12 0 0 0 0 0 0 1 0 13 0 0 0 0 0 0 0 1 In [50]: x = df.stack() # I don't think you actually need to specify ALL of the categories here, as by definition # they are in the dummy matrix to start (and hence the column index) In [51]: Series(pd.Categorical(x[x!=0].index.get_level_values(1))) Out[51]: 0 a 1 a 2 a 3 b 4 b 5 b 6 c 7 c 8 d 9 d 10 e 11 f 12 g 13 h Name: level_1, dtype: category Categories (8, object): [a < b < c < d < e < f < g < h] 

Así que creo que necesitamos una función para 'hacer' esto, ya que parece ser una operación natural. Tal vez get_categories() , vea aquí

Han pasado algunos años, por lo que puede que no haya estado en el kit de herramientas de los pandas cuando se hizo esta pregunta originalmente, pero este enfoque me parece un poco más fácil. idxmax devolverá el índice correspondiente al elemento más grande (es decir, el que tiene un 1 ). Hacemos axis=1 porque queremos el nombre de la columna donde aparece el 1 .

EDIT: No me molesté en hacerlo categórico en lugar de solo una cadena, pero puede hacerlo de la misma manera que lo hizo @Jeff envolviéndolo con pd.Categorical (y pd.Series , si lo desea).

 In [1]: import pandas as pd In [2]: s = pd.Series(['a', 'b', 'a', 'c']) In [3]: s Out[3]: 0 a 1 b 2 a 3 c dtype: object In [4]: dummies = pd.get_dummies(s) In [5]: dummies Out[5]: abc 0 1 0 0 1 0 1 0 2 1 0 0 3 0 0 1 In [6]: s2 = dummies.idxmax(axis=1) In [7]: s2 Out[7]: 0 a 1 b 2 a 3 c dtype: object In [8]: (s2 == s).all() Out[8]: True 

EDITAR en respuesta al comentario de @ piRSquared: Esta solución asume que hay un 1 por fila. Creo que este suele ser el formato que tiene uno. pd.get_dummies puede devolver filas que son todas 0 si tiene drop_first=True o si hay valores de NaN y dummy_na=False (predeterminado) (¿faltan algunos casos?). Una fila de todos los ceros se tratará como si fuera una instancia de la variable nombrada en la primera columna (por ejemplo, a en el ejemplo anterior).

Si drop_first=True , no tiene forma de saber del dataframe de los simuladores solo cuál era el nombre de la “primera” variable, por lo que la operación no se puede invertir a menos que mantenga información adicional a su alrededor; Recomiendo dejar drop_first=False (por defecto).

Dado que el valor predeterminado es dummy_na=False , esto podría causar problemas. Configure dummy_na=True cuando llame a pd.get_dummies si desea usar esta solución para invertir el “dummification” y sus datos contienen cualquier NaNs . Si configura dummy_na=True , siempre se agregará una columna “nan”, incluso si esa columna es 0, por lo que es probable que no desee configurar esto a menos que tenga NaN s. Un buen enfoque podría ser establecer dummies = pd.get_dummies(series, dummy_na=series.isnull().any()) . Lo que también es bueno es que la solución idxmax regenerará correctamente tus NaN s (no solo una cadena que dice “nan”).

También vale la pena mencionar que la configuración de drop_first=True y dummy_na=False significa que los NaN vuelven indistinguibles de una instancia de la primera variable, por lo que debe desaconsejarse si su conjunto de datos puede contener valores de NaN .

Esta es una respuesta bastante tardía, pero como solicita una forma rápida de hacerlo, asumo que está buscando la estrategia más eficaz. En un dataframe grande (por ejemplo, 10000 filas), puede obtener un aumento de velocidad muy significativo utilizando np.where lugar de idxmax o get_level_values , y obtener el mismo resultado. La idea es indexar los nombres de columna donde el dataframe ficticio no es 0:

Método:

Usando los mismos datos de muestra que @Nathan:

 >>> dummies abc 0 1 0 0 1 0 1 0 2 1 0 0 3 0 0 1 s2 = pd.Series(dummies.columns[np.where(dummies!=0)[1]]) >>> s2 0 a 1 b 2 a 3 c dtype: object 

Punto de referencia:

En un pequeño dataframe ficticio, no verá mucha diferencia en el rendimiento. Sin embargo, probar diferentes estrategias para resolver este problema en una serie grande:

 s = pd.Series(np.random.choice(['a','b','c'], 10000)) dummies = pd.get_dummies(s) def np_method(dummies=dummies): return pd.Series(dummies.columns[np.where(dummies!=0)[1]]) def idx_max_method(dummies=dummies): return dummies.idxmax(axis=1) def get_level_values_method(dummies=dummies): x = dummies.stack() return pd.Series(pd.Categorical(x[x!=0].index.get_level_values(1))) def dot_method(dummies=dummies): return dummies.dot(dummies.columns) import timeit # Time each method, 1000 iterations each: >>> timeit.timeit(np_method, number=1000) 1.0491090340074152 >>> timeit.timeit(idx_max_method, number=1000) 12.119140846014488 >>> timeit.timeit(get_level_values_method, number=1000) 4.109266621991992 >>> timeit.timeit(dot_method, number=1000) 1.6741622970002936 

El método np.where es aproximadamente 4 veces más rápido que el método get_level_values 11.5 veces más rápido que el método idxmax . También supera (pero solo un poco) el método .dot() descrito en esta respuesta a una pregunta similar

Todos ellos devuelven el mismo resultado:

 >>> (get_level_values_method() == np_method()).all() True >>> (idx_max_method() == np_method()).all() True 

Preparar

Usando la configuración de @Jeff

 s = Series(list('aaabbbccddefgh')).astype('category') df = pd.get_dummies(s) 

Si las columnas son cadenas

y solo hay un 1 por fila

 df.dot(df.columns) 0 a 1 a 2 a 3 b 4 b 5 b 6 c 7 c 8 d 9 d 10 e 11 f 12 g 13 h dtype: object 

numpy.where

¡Otra vez! Suponiendo solo un 1 por fila

 i, j = np.where(df) pd.Series(df.columns[j], i) 0 a 1 a 2 a 3 b 4 b 5 b 6 c 7 c 8 d 9 d 10 e 11 f 12 g 13 h dtype: category Categories (8, object): [a, b, c, d, e, f, g, h] 

numpy.where

No asumiendo un 1 por fila.

 i, j = np.where(df) pd.Series(dict(zip(zip(i, j), df.columns[j]))) 0 0 a 1 0 a 2 0 a 3 1 b 4 1 b 5 1 b 6 2 c 7 2 c 8 3 d 9 3 d 10 4 e 11 5 f 12 6 g 13 7 h dtype: object 

numpy.where

Donde no asumimos un 1 por fila y soltamos el índice.

 i, j = np.where(df) pd.Series(dict(zip(zip(i, j), df.columns[j]))).reset_index(-1, drop=True) 0 a 1 a 2 a 3 b 4 b 5 b 6 c 7 c 8 d 9 d 10 e 11 f 12 g 13 h dtype: object