Come realizzare buoni esempi di panda riproducibili

Nov 21 2013

Avendo passato una discreta quantità di tempo a guardare i tag r e pandas su SO, l'impressione che ho è che le pandasdomande hanno meno probabilità di contenere dati riproducibili. Questo è qualcosa che la comunità R è stata piuttosto brava nell'incoraggiare e, grazie a guide come questa , i nuovi arrivati ​​sono in grado di ottenere un aiuto per mettere insieme questi esempi. Le persone che sono in grado di leggere queste guide e tornare con dati riproducibili spesso avranno molta più fortuna nell'ottenere risposte alle loro domande.

Come possiamo creare buoni esempi riproducibili per le pandasdomande? È possibile mettere insieme semplici dataframe, ad esempio:

import pandas as pd
df = pd.DataFrame({'user': ['Bob', 'Jane', 'Alice'], 
                   'income': [40000, 50000, 42000]})

Ma molti set di dati di esempio richiedono una struttura più complicata, ad esempio:

  • datetime indici o dati
  • Variabili categoriali multiple (esiste un equivalente alla expand.grid()funzione di R , che produce tutte le possibili combinazioni di alcune variabili date?)
  • Dati MultiIndex o Panel

Per i set di dati che sono difficili da modellare utilizzando poche righe di codice, esiste un equivalente a R dput()che ti consente di generare codice copia-incollabile per rigenerare la tua struttura dati?

Risposte

362 AndyHayden Nov 23 2013 at 13:19

Nota: le idee qui sono piuttosto generiche per Stack Overflow, anzi domande .

Disclaimer: scrivere una buona domanda è DIFFICILE.

Il bene:

  • includere un piccolo * esempio DataFrame, come codice eseguibile:

    In [1]: df = pd.DataFrame([[1, 2], [1, 3], [4, 6]], columns=['A', 'B'])
    

    o renderlo "copia e incollabile" utilizzando pd.read_clipboard(sep='\s\s+'), puoi formattare il testo per l'evidenziazione di Stack Overflow e utilizzare Ctrl+ K(o anteporre quattro spazi a ciascuna riga), oppure posizionare tre tilde sopra e sotto il codice con il codice non rientrato:

    In [2]: df
    Out[2]: 
       A  B
    0  1  2
    1  1  3
    2  4  6
    

    pd.read_clipboard(sep='\s\s+')mettiti alla prova .

    * Intendo davvero piccolo , la stragrande maggioranza dei DataFrame di esempio potrebbe contenere meno di 6 righe di citazione necessaria , e scommetto che posso farlo su 5 righe. Puoi riprodurre l'errore con df = df.head(), se non giocherellare per vedere se riesci a creare un piccolo DataFrame che mostri il problema che stai affrontando.

    * Ogni regola ha un'eccezione, la più ovvia è per problemi di prestazioni ( in questo caso sicuramente utilizzare% timeit e possibilmente% Prun ), dove si dovrebbe generare (considerare l'utilizzo di np.random.seed così abbiamo la stessa identica struttura): df = pd.DataFrame(np.random.randn(100000000, 10)). Dire che "rendi questo codice veloce per me" non è strettamente pertinente per il sito ...

  • scrivi il risultato che desideri (come sopra)

    In [3]: iwantthis
    Out[3]: 
       A  B
    0  1  5
    1  4  6
    

    Spiega da cosa provengono i numeri: il 5 è la somma della colonna B per le righe in cui A è 1.

  • mostra il codice che hai provato:

    In [4]: df.groupby('A').sum()
    Out[4]: 
       B
    A   
    1  5
    4  6
    

    Ma dì cosa non è corretto: la colonna A è nell'indice piuttosto che in una colonna.

  • mostra che hai fatto qualche ricerca ( cerca nei documenti , cerca StackOverflow ), fornisci un riepilogo:

    La docstring per sum dice semplicemente "Calcola la somma dei valori di gruppo"

    I documenti di groupby non forniscono alcun esempio per questo.

    A parte: la risposta qui è da usare df.groupby('A', as_index=False).sum().

  • se è rilevante che tu abbia colonne Timestamp, ad esempio stai ricampionando o qualcosa del genere, sii esplicito e applicale pd.to_datetimeper buona misura **.

    df['date'] = pd.to_datetime(df['date']) # this column ought to be date..
    

    ** A volte questo è il problema stesso: erano stringhe.

Il cattivo:

  • non includere un MultiIndex, che non possiamo copiare e incollare (vedi sopra), questo è un po 'un reclamo con la visualizzazione predefinita dei panda ma comunque fastidioso:

    In [11]: df
    Out[11]:
         C
    A B   
    1 2  3
      2  6
    

    Il modo corretto è includere un normale DataFrame con una set_indexchiamata:

    In [12]: df = pd.DataFrame([[1, 2, 3], [1, 2, 6]], columns=['A', 'B', 'C']).set_index(['A', 'B'])
    
    In [13]: df
    Out[13]: 
         C
    A B   
    1 2  3
      2  6
    
  • fornisci informazioni su cosa sia quando fornisci il risultato che desideri:

       B
    A   
    1  1
    5  0
    

    Sii specifico su come hai ottenuto i numeri (cosa sono) ... ricontrolla che siano corretti.

  • Se il tuo codice genera un errore, includi l'intera traccia dello stack (questa può essere modificata in seguito se è troppo rumorosa). Mostra il numero di riga (e la riga corrispondente del tuo codice contro cui sta sollevando).

Il brutto:

  • non collegare a un csv a cui non abbiamo accesso (idealmente non collegare affatto a una fonte esterna ...)

    df = pd.read_csv('my_secret_file.csv')  # ideally with lots of parsing options
    

    La maggior parte dei dati è di proprietà , otteniamo questo: crea dati simili e vedi se riesci a riprodurre il problema (qualcosa di piccolo).

  • non spiegare la situazione vagamente a parole, come se avessi un DataFrame "grande", menziona alcuni dei nomi delle colonne di passaggio (assicurati di non menzionare i loro dtypes). Cerca di entrare nei dettagli di qualcosa che è completamente privo di significato senza vedere il contesto reale. Presumibilmente nessuno leggerà nemmeno fino alla fine di questo paragrafo.

    I saggi sono cattivi, è più facile con piccoli esempi.

  • non includere 10+ (100+ ??) righe di dati munging prima di arrivare alla tua domanda effettiva.

    Per favore, ne vediamo abbastanza nei nostri lavori quotidiani. Noi vogliamo aiutare, ma non è così ... .
    Taglia l'introduzione e mostra semplicemente i DataFrame rilevanti (o le loro versioni piccole) nel passaggio che ti causa problemi.

Comunque, divertiti ad imparare Python, NumPy e Panda!

77 JohnE May 24 2015 at 21:22

Come creare set di dati di esempio

Questo è principalmente per espandere la risposta di @ AndyHayden fornendo esempi di come creare frame di dati di esempio. Pandas e (soprattutto) numpy ti offrono una varietà di strumenti per questo in modo che in genere puoi creare un facsimile ragionevole di qualsiasi set di dati reale con solo poche righe di codice.

Dopo aver importato numpy e panda, assicurati di fornire un seme casuale se vuoi che le persone siano in grado di riprodurre esattamente i tuoi dati e risultati.

import numpy as np
import pandas as pd

np.random.seed(123)

Un esempio di lavello da cucina

Ecco un esempio che mostra una varietà di cose che puoi fare. Tutti i tipi di frame di dati di esempio utili potrebbero essere creati da un sottoinsieme di questo:

df = pd.DataFrame({ 

    # some ways to create random data
    'a':np.random.randn(6),
    'b':np.random.choice( [5,7,np.nan], 6),
    'c':np.random.choice( ['panda','python','shark'], 6),

    # some ways to create systematic groups for indexing or groupby
    # this is similar to r's expand.grid(), see note 2 below
    'd':np.repeat( range(3), 2 ),
    'e':np.tile(   range(2), 3 ),

    # a date range and set of random dates
    'f':pd.date_range('1/1/2011', periods=6, freq='D'),
    'g':np.random.choice( pd.date_range('1/1/2011', periods=365, 
                          freq='D'), 6, replace=False) 
    })

Questo produce:

          a   b       c  d  e          f          g
0 -1.085631 NaN   panda  0  0 2011-01-01 2011-08-12
1  0.997345   7   shark  0  1 2011-01-02 2011-11-10
2  0.282978   5   panda  1  0 2011-01-03 2011-10-30
3 -1.506295   7  python  1  1 2011-01-04 2011-09-07
4 -0.578600 NaN   shark  2  0 2011-01-05 2011-02-27
5  1.651437   7  python  2  1 2011-01-06 2011-02-03

Alcune note:

  1. np.repeate np.tile(colonne de e) sono molto utili per creare gruppi e indici in modo molto regolare. Per 2 colonne, questo può essere utilizzato per duplicare facilmente le r, expand.grid()ma è anche più flessibile nella capacità di fornire un sottoinsieme di tutte le permutazioni. Tuttavia, per 3 o più colonne la sintassi diventa rapidamente ingombrante.
  2. Per una sostituzione più diretta delle r, expand.grid()vedere la itertoolssoluzione nel ricettario dei panda o la np.meshgridsoluzione mostrata qui . Quelle consentiranno qualsiasi numero di dimensioni.
  3. Puoi fare un bel po 'con np.random.choice. Ad esempio, nella colonna g, abbiamo una selezione casuale di 6 date del 2011. Inoltre, impostando replace=Falsepossiamo garantire che queste date siano univoche - molto utile se vogliamo usarlo come indice con valori univoci.

Dati di mercato azionario falsi

Oltre a prendere sottoinsiemi del codice precedente, puoi combinare ulteriormente le tecniche per fare praticamente qualsiasi cosa. Ad esempio, ecco un breve esempio che combina np.tilee date_rangecrea dati di ticker di esempio per 4 titoli che coprono le stesse date:

stocks = pd.DataFrame({ 
    'ticker':np.repeat( ['aapl','goog','yhoo','msft'], 25 ),
    'date':np.tile( pd.date_range('1/1/2011', periods=25, freq='D'), 4 ),
    'price':(np.random.randn(100).cumsum() + 10) })

Ora abbiamo un set di dati di esempio con 100 righe (25 date per ticker), ma abbiamo usato solo 4 righe per farlo, rendendo facile la riproduzione per tutti gli altri senza copiare e incollare 100 righe di codice. Puoi quindi visualizzare sottoinsiemi di dati se aiuta a spiegare la tua domanda:

>>> stocks.head(5)

        date      price ticker
0 2011-01-01   9.497412   aapl
1 2011-01-02  10.261908   aapl
2 2011-01-03   9.438538   aapl
3 2011-01-04   9.515958   aapl
4 2011-01-05   7.554070   aapl

>>> stocks.groupby('ticker').head(2)

         date      price ticker
0  2011-01-01   9.497412   aapl
1  2011-01-02  10.261908   aapl
25 2011-01-01   8.277772   goog
26 2011-01-02   7.714916   goog
50 2011-01-01   5.613023   yhoo
51 2011-01-02   6.397686   yhoo
75 2011-01-01  11.736584   msft
76 2011-01-02  11.944519   msft
51 piRSquared Jul 20 2016 at 01:35

Diario di un risponditore

Il mio miglior consiglio per porre domande sarebbe giocare sulla psicologia delle persone che rispondono alle domande. Essendo una di quelle persone, posso dare un'idea del perché rispondo a certe domande e perché non rispondo ad altre.

Motivazioni

Sono motivato a rispondere alle domande per diversi motivi

  1. Stackoverflow.com è stata una risorsa estremamente preziosa per me. Volevo restituire.
  2. Nei miei sforzi per restituire, ho scoperto che questo sito è una risorsa ancora più potente di prima. Rispondere alle domande è un'esperienza di apprendimento per me e mi piace imparare. Leggi questa risposta e commenta da un altro veterinario . Questo tipo di interazione mi rende felice.
  3. Mi piacciono i punti!
  4. Vedi # 3.
  5. Mi piacciono i problemi interessanti.

Tutte le mie intenzioni più pure sono grandi e tutte, ma ottengo quella soddisfazione se rispondo a 1 o 30 domande. Ciò che guida le mie scelte per quali domande rispondere ha un'enorme componente di massimizzazione del punto.

Trascorrerò anche del tempo su problemi interessanti, ma questo è raro e non aiuta un richiedente che ha bisogno di una soluzione a una domanda non interessante. La soluzione migliore per farmi rispondere a una domanda è di servire quella domanda su un piatto pronto per consentirmi di rispondere con il minimo sforzo possibile. Se sto guardando due domande e una ha il codice posso copiare e incollare per creare tutte le variabili di cui ho bisogno ... prendo quella! Tornerò sull'altro se avrò tempo, forse.

Consiglio principale

Rendi più facile per le persone che rispondono alle domande.

  • Fornisci codice che crei le variabili necessarie.
  • Riduci al minimo il codice. Se i miei occhi si velano mentre guardo il post, passo alla domanda successiva o torno a qualsiasi altra cosa stia facendo.
  • Pensa a quello che stai chiedendo e sii specifico. Vogliamo vedere cosa hai fatto perché le lingue naturali (inglese) sono inesatte e confuse. Esempi di codice di ciò che hai provato aiutano a risolvere le incongruenze in una descrizione in linguaggio naturale.
  • PER FAVORE, mostra cosa ti aspetti !!! Devo sedermi e provare le cose. Non conosco quasi mai la risposta a una domanda senza provare alcune cose. Se non vedo un esempio di quello che stai cercando, potrei passare la domanda perché non ho voglia di indovinare.

La tua reputazione non è solo la tua reputazione.

Mi piacciono i punti (l'ho detto sopra). Ma quei punti non sono davvero la mia reputazione. La mia vera reputazione è una fusione di ciò che gli altri sul sito pensano di me. Mi sforzo di essere giusto e onesto e spero che gli altri lo capiscano. Ciò che significa per un richiedente è che ricordiamo i comportamenti di chi chiede. Se non selezioni le risposte e voti bene le risposte, me lo ricordo. Se ti comporti in modi che non mi piacciono o in modi che mi piacciono, me lo ricordo. Questo gioca anche a quali domande risponderò.


Ad ogni modo, probabilmente posso andare avanti, ma risparmierò tutti voi che avete letto questo.

31 Alexander Sep 12 2015 at 14:06

La sfida Uno degli aspetti più impegnativi nel rispondere alle domande SO è il tempo necessario per ricreare il problema (compresi i dati). È meno probabile che venga data risposta alle domande che non hanno un modo chiaro per riprodurre i dati. Dato che ti stai dedicando del tempo a scrivere una domanda e hai un problema per il quale vorresti aiuto, puoi facilmente aiutare te stesso fornendo dati che altri possono utilizzare per risolvere il tuo problema.

Le istruzioni fornite da @ Anddy per scrivere buone domande sui Panda sono un ottimo punto di partenza. Per ulteriori informazioni, fare riferimento a come chiedere e come creare esempi minimi, completi e verificabili .

Indica chiaramente la tua domanda in anticipo. Dopo aver dedicato del tempo a scrivere la tua domanda e qualsiasi codice di esempio, prova a leggerlo e fornire un "Riepilogo esecutivo" per il tuo lettore che riassume il problema e indica chiaramente la domanda.

Domanda originale :

Ho questi dati ...

Voglio farlo...

Voglio che il mio risultato assomigli a questo ...

Tuttavia, quando provo a fare [questo], ottengo il seguente problema ...

Ho provato a trovare soluzioni facendo [questo] e [quello].

Come lo aggiusto?

A seconda della quantità di dati, del codice di esempio e degli stack di errori forniti, il lettore deve fare molto prima di capire qual è il problema. Prova a ripetere la tua domanda in modo che la domanda stessa sia in primo piano, quindi fornisci i dettagli necessari.

Domanda rivista :

Domanda: Come posso fare [questo]?

Ho provato a trovare soluzioni facendo [questo] e [quello].

Quando ho provato a fare [questo], ottengo il seguente problema ...

Vorrei che i miei risultati finali fossero così ...

Ecco un po 'di codice minimo che può riprodurre il mio problema ...

Ed ecco come ricreare i miei dati di esempio: df = pd.DataFrame({'A': [...], 'B': [...], ...})

FORNIRE DATI DI CAMPIONE SE NECESSARIO !!!

A volte basta solo la testa o la coda del DataFrame. È inoltre possibile utilizzare i metodi proposti da @JohnE per creare set di dati più grandi che possono essere riprodotti da altri. Usando il suo esempio per generare un DataFrame di 100 righe di prezzi delle azioni:

stocks = pd.DataFrame({ 
    'ticker':np.repeat( ['aapl','goog','yhoo','msft'], 25 ),
    'date':np.tile( pd.date_range('1/1/2011', periods=25, freq='D'), 4 ),
    'price':(np.random.randn(100).cumsum() + 10) })

Se questi erano i tuoi dati effettivi, potresti semplicemente includere la testa e / o la coda del dataframe come segue (assicurati di rendere anonimi i dati sensibili):

>>> stocks.head(5).to_dict()
{'date': {0: Timestamp('2011-01-01 00:00:00'),
  1: Timestamp('2011-01-01 00:00:00'),
  2: Timestamp('2011-01-01 00:00:00'),
  3: Timestamp('2011-01-01 00:00:00'),
  4: Timestamp('2011-01-02 00:00:00')},
 'price': {0: 10.284260107718254,
  1: 11.930300761831457,
  2: 10.93741046217319,
  3: 10.884574289565609,
  4: 11.78005850418319},
 'ticker': {0: 'aapl', 1: 'aapl', 2: 'aapl', 3: 'aapl', 4: 'aapl'}}

>>> pd.concat([stocks.head(), stocks.tail()], ignore_index=True).to_dict()
{'date': {0: Timestamp('2011-01-01 00:00:00'),
  1: Timestamp('2011-01-01 00:00:00'),
  2: Timestamp('2011-01-01 00:00:00'),
  3: Timestamp('2011-01-01 00:00:00'),
  4: Timestamp('2011-01-02 00:00:00'),
  5: Timestamp('2011-01-24 00:00:00'),
  6: Timestamp('2011-01-25 00:00:00'),
  7: Timestamp('2011-01-25 00:00:00'),
  8: Timestamp('2011-01-25 00:00:00'),
  9: Timestamp('2011-01-25 00:00:00')},
 'price': {0: 10.284260107718254,
  1: 11.930300761831457,
  2: 10.93741046217319,
  3: 10.884574289565609,
  4: 11.78005850418319,
  5: 10.017209045035006,
  6: 10.57090128181566,
  7: 11.442792747870204,
  8: 11.592953372130493,
  9: 12.864146419530938},
 'ticker': {0: 'aapl',
  1: 'aapl',
  2: 'aapl',
  3: 'aapl',
  4: 'aapl',
  5: 'msft',
  6: 'msft',
  7: 'msft',
  8: 'msft',
  9: 'msft'}}

Potresti anche voler fornire una descrizione del DataFrame (usando solo le colonne pertinenti). Ciò rende più facile per gli altri controllare i tipi di dati di ciascuna colonna e identificare altri errori comuni (ad esempio date come stringa vs datetime64 vs oggetto):

stocks.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 100 entries, 0 to 99
Data columns (total 3 columns):
date      100 non-null datetime64[ns]
price     100 non-null float64
ticker    100 non-null object
dtypes: datetime64[ns](1), float64(1), object(1)

NOTA: se il tuo DataFrame ha un MultiIndex:

Se il tuo DataFrame ha un multiindice, devi prima reimpostarlo prima di chiamare to_dict. È quindi necessario ricreare l'indice utilizzando set_index:

# MultiIndex example.  First create a MultiIndex DataFrame.
df = stocks.set_index(['date', 'ticker'])
>>> df
                       price
date       ticker           
2011-01-01 aapl    10.284260
           aapl    11.930301
           aapl    10.937410
           aapl    10.884574
2011-01-02 aapl    11.780059
...

# After resetting the index and passing the DataFrame to `to_dict`, make sure to use 
# `set_index` to restore the original MultiIndex.  This DataFrame can then be restored.

d = df.reset_index().to_dict()
df_new = pd.DataFrame(d).set_index(['date', 'ticker'])
>>> df_new.head()
                       price
date       ticker           
2011-01-01 aapl    10.284260
           aapl    11.930301
           aapl    10.937410
           aapl    10.884574
2011-01-02 aapl    11.780059
15 sds Dec 17 2016 at 00:57

Ecco la mia versione di dput- lo strumento R standard per produrre report riproducibili - per Pandas DataFrames. Probabilmente fallirà per frame più complessi, ma sembra che funzioni in casi semplici:

import pandas as pd
def dput(x):
    if isinstance(x,pd.Series):
        return "pd.Series(%s,dtype='%s',index=pd.%s)" % (list(x),x.dtype,x.index)
    if isinstance(x,pd.DataFrame):
        return "pd.DataFrame({" + ", ".join([
            "'%s': %s" % (c,dput(x[c])) for c in x.columns]) + (
                "}, index=pd.%s)" % (x.index))
    raise NotImplementedError("dput",type(x),x)

adesso,

df = pd.DataFrame({'a':[1,2,3,4,2,1,3,1]})
assert df.equals(eval(dput(df)))
du = pd.get_dummies(df.a,"foo")
assert du.equals(eval(dput(du)))
di = df
di.index = list('abcdefgh')
assert di.equals(eval(dput(di)))

Nota che questo produce un output molto più dettagliato rispetto DataFrame.to_dict, ad esempio,

pd.DataFrame({
  'foo_1':pd.Series([1, 0, 0, 0, 0, 1, 0, 1],dtype='uint8',index=pd.RangeIndex(start=0, stop=8, step=1)),
  'foo_2':pd.Series([0, 1, 0, 0, 1, 0, 0, 0],dtype='uint8',index=pd.RangeIndex(start=0, stop=8, step=1)),
  'foo_3':pd.Series([0, 0, 1, 0, 0, 0, 1, 0],dtype='uint8',index=pd.RangeIndex(start=0, stop=8, step=1)),
  'foo_4':pd.Series([0, 0, 0, 1, 0, 0, 0, 0],dtype='uint8',index=pd.RangeIndex(start=0, stop=8, step=1))},
  index=pd.RangeIndex(start=0, stop=8, step=1))

vs

{'foo_1': {0: 1, 1: 0, 2: 0, 3: 0, 4: 0, 5: 1, 6: 0, 7: 1}, 
 'foo_2': {0: 0, 1: 1, 2: 0, 3: 0, 4: 1, 5: 0, 6: 0, 7: 0}, 
 'foo_3': {0: 0, 1: 0, 2: 1, 3: 0, 4: 0, 5: 0, 6: 1, 7: 0}, 
 'foo_4': {0: 0, 1: 0, 2: 0, 3: 1, 4: 0, 5: 0, 6: 0, 7: 0}}

per quanto dusopra, ma conserva i tipi di colonna . Ad esempio, nel caso di test sopra,

du.equals(pd.DataFrame(du.to_dict()))
==> False

perché du.dtypesè uint8ed pd.DataFrame(du.to_dict()).dtypesè int64.