2015-09-18 21 views
5

Vorrei utilizzare pandas e statsmodels per adattare un modello lineare su sottoinsiemi di un dataframe e restituire i valori previsti. Tuttavia, sto avendo difficoltà a capire l'idioma panda giusto da usare. Ecco quello che sto cercando di fare:Il gruppo panda può trasformare un DataFrame in una serie?

import pandas as pd 
import statsmodels.formula.api as sm 
import seaborn as sns 

tips = sns.load_dataset("tips") 
def fit_predict(df): 
    m = sm.ols("tip ~ total_bill", df).fit() 
    return pd.Series(m.predict(df), index=df.index) 
tips["predicted_tip"] = tips.groupby("day").transform(fit_predict) 

Questo solleva il seguente errore:

--------------------------------------------------------------------------- 
ValueError        Traceback (most recent call last) 
<ipython-input-139-b3d2575e2def> in <module>() 
----> 1 tips["predicted_tip"] = tips.groupby("day").transform(fit_predict) 

/Users/mwaskom/anaconda/lib/python2.7/site-packages/pandas/core/groupby.pyc in transform(self, func, *args, **kwargs) 
    3033      return self._transform_general(func, *args, **kwargs) 
    3034   except: 
-> 3035    return self._transform_general(func, *args, **kwargs) 
    3036 
    3037   # a reduction transform 

/Users/mwaskom/anaconda/lib/python2.7/site-packages/pandas/core/groupby.pyc in _transform_general(self, func, *args, **kwargs) 
    2988      group.T.values[:] = res 
    2989     else: 
-> 2990      group.values[:] = res 
    2991 
    2992     applied.append(group) 

ValueError: could not broadcast input array from shape (62) into shape (62,6) 

L'errore ha un senso in quanto penso .transform vuole mappare un dataframe ad un dataframe. Ma esiste un modo per eseguire un'operazione di groupby su un DataFrame, passare ogni blocco in una funzione che lo riduce a una serie (con lo stesso indice) e quindi combinare le serie risultanti in qualcosa che può essere inserito nel dataframe originale?

risposta

2

La parte superiore qui è lo stesso, sto solo usando un set di dati giocattolo b/c io sono dietro un firewall.

tips = pd.DataFrame({ 'day':list('MMMFFF'), 'tip':range(6), 
         'total_bill':[10,40,20,80,50,40] }) 

def fit_predict(df): 
    m = sm.ols("tip ~ total_bill", df).fit() 
    return pd.Series(m.predict(df), index=df.index) 

Se si cambia 'trasformare' a 'applicare', si otterrà:

tips.groupby("day").apply(fit_predict) 

day 
F 3 2.923077 
    4 4.307692 
    5 4.769231 
M 0 0.714286 
    1 1.357143 
    2 0.928571 

questo non è del tutto ciò che si vuole, ma se si elimina il livello = 0, si può procedere, se lo desideri :

tips['predicted'] = tips.groupby("day").apply(fit_predict).reset_index(level=0,drop=True) 

    day tip total_bill predicted 
0 M 0   10 0.714286 
1 M 1   40 1.357143 
2 M 2   20 0.928571 
3 F 3   80 2.923077 
4 F 4   50 4.307692 
5 F 5   40 4.769231 
+1

Interessante, questo non funziona con il set di dati di Seaborn Tips a causa di un errore che implica che 'day' è un oggetto categoriale. Mi chiedo se sia un bug nei panda. – mwaskom

+0

Funziona su master panda. C'era un bug con Categoricals che non aveva un flag per joins/concats. – TomAugspurger

+0

Fresco. @ Tom Augspurger, diresti che questo è il modo più idiomatico per farlo in Panda? Segnalo correttamente se è così. – mwaskom

0

EDIT:

q.gps.apply(lambda df: df.join(q.fit_predict(df)))

ho dovuto modificare la funzione fit_predict dare un nome al Series.

def fit_predict(df): 
m = sm.ols("tip ~ total_bill", df).fit() 
s = pd.Series(m.predict(df), index=df.index) 
s.name = 'Prediction' #EDIT 
return s 
+0

Si noterà, tuttavia, che non funziona sull'esempio indicato nella domanda. – mwaskom

+0

Non è rilevante perché '.describe' associa un' DataFrame' a un 'DataFrame', non un' DataFrame' a un 'Series'. – mwaskom

+0

Sei corretto. Ho modificato la mia risposta per passare un DataFrame alla funzione DataFrame a 'transform'. –