Cómo hacer buenos ejemplos de pandas reproducibles

Nov 21 2013

Después de haber pasado una cantidad de tiempo decente observando las etiquetas r y pandas en SO, la impresión que tengo es que pandases menos probable que las preguntas contengan datos reproducibles. Esto es algo que la comunidad de R ha alentado bastante bien y, gracias a guías como esta , los recién llegados pueden obtener ayuda para armar estos ejemplos. Las personas que pueden leer estas guías y volver con datos reproducibles a menudo tendrán mucha más suerte al obtener respuestas a sus preguntas.

¿Cómo podemos crear buenos ejemplos reproducibles para pandaspreguntas? Se pueden juntar tramas de datos simples, por ejemplo:

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

Pero muchos conjuntos de datos de ejemplo necesitan una estructura más complicada, por ejemplo:

  • datetime índices o datos
  • Varias variables categóricas (¿existe un equivalente a la expand.grid()función de R , que produce todas las combinaciones posibles de algunas variables dadas?)
  • Datos de MultiIndex o Panel

Para los conjuntos de datos que son difíciles de simular con unas pocas líneas de código, ¿existe un equivalente a las R dput()que le permita generar código que se pueda copiar y pegar para regenerar su estructura de datos?

Respuestas

362 AndyHayden Nov 23 2013 at 13:19

Nota: Las ideas aquí son bastante genéricas para Stack Overflow, de hecho preguntas .

Descargo de responsabilidad: escribir una buena pregunta es DIFÍCIL.

El bueno:

  • incluya un pequeño * ejemplo de DataFrame, ya sea como código ejecutable:

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

    o hacerlo "copiar y pegar" usando pd.read_clipboard(sep='\s\s+'), puede formatear el texto para resaltar Stack Overflow y usar Ctrl+ K(o anteponer cuatro espacios a cada línea), o colocar tres tildes arriba y abajo de su código con su código sin sangría:

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

    pd.read_clipboard(sep='\s\s+')ponte a prueba .

    * Realmente me refiero a pequeños , la gran mayoría de los DataFrames de ejemplo podrían tener menos de 6 filas de citas necesarias , y apuesto a que puedo hacerlo en 5 filas. ¿Puede reproducir el error con df = df.head(), si no juguetear, para ver si puede crear un pequeño DataFrame que muestre el problema al que se enfrenta?

    * Cada regla tiene una excepción, la más obvia es para problemas de rendimiento ( en cuyo caso, sin duda utilizar% timeit y posiblemente% prun ), donde se debe generar (np.random.seed considerar el uso de lo que tenemos exactamente el mismo marco): df = pd.DataFrame(np.random.randn(100000000, 10)). Decir eso, "haz que este código sea rápido para mí" no es estrictamente un tema para el sitio ...

  • escriba el resultado que desea (similar al anterior)

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

    Explica de qué proceden los números: el 5 es la suma de la columna B para las filas donde A es 1.

  • muestra el código que has probado:

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

    Pero di lo que es incorrecto: la columna A está en el índice en lugar de una columna.

  • demuestre que ha investigado un poco ( busque los documentos , busque StackOverflow ), dé un resumen:

    La cadena de documentación para suma simplemente indica "Calcular la suma de los valores del grupo"

    Los documentos de groupby no dan ningún ejemplo para esto.

    Aparte: la respuesta aquí es usar df.groupby('A', as_index=False).sum().

  • si es relevante que tenga columnas de marca de tiempo, por ejemplo, está remuestreando o algo así, sea explícito y aplíquelas pd.to_datetimeen una buena medida **.

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

    ** A veces, este es el problema en sí mismo: eran cadenas.

El malo:

  • no incluye un MultiIndex, que no podemos copiar y pegar (ver más arriba), esto es una especie de queja con la pantalla predeterminada de pandas pero, no obstante, molesto:

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

    La forma correcta es incluir un DataFrame ordinario con una set_indexllamada:

    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
    
  • proporcione información sobre lo que es al dar el resultado que desea:

       B
    A   
    1  1
    5  0
    

    Sea específico acerca de cómo obtuvo los números (cuáles son) ... verifique que sean correctos.

  • Si su código arroja un error, incluya todo el seguimiento de la pila (esto se puede editar más adelante si es demasiado ruidoso). Muestre el número de línea (y la línea correspondiente de su código contra la cual se está levantando).

El feo:

  • no se vincule a un csv al que no tenemos acceso (lo ideal es que no se vincule a una fuente externa en absoluto ...)

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

    La mayoría de los datos son propietarios , obtenemos eso: invente datos similares y vea si puede reproducir el problema (algo pequeño).

  • no explique la situación vagamente con palabras, como si tuviera un DataFrame que es "grande", mencione algunos de los nombres de las columnas de pasada (asegúrese de no mencionar sus dtypes). Intente entrar en muchos detalles sobre algo que no tiene ningún sentido sin ver el contexto real. Es de suponer que nadie va a leer siquiera hasta el final de este párrafo.

    Los ensayos son malos, es más fácil con pequeños ejemplos.

  • no incluya más de 10 (100+ ??) líneas de intercambio de datos antes de llegar a su pregunta real.

    Por favor, vemos suficiente de esto en nuestros trabajos diarios. Queremos ayudar, pero no así… .
    Corte la introducción y simplemente muestre los DataFrames relevantes (o versiones pequeñas de ellos) en el paso que le está causando problemas.

De todos modos, ¡diviértete aprendiendo Python, NumPy y Pandas!

77 JohnE May 24 2015 at 21:22

Cómo crear conjuntos de datos de muestra

Esto es principalmente para expandir la respuesta de @ AndyHayden al proporcionar ejemplos de cómo puede crear marcos de datos de muestra. Pandas y (especialmente) numpy le brindan una variedad de herramientas para esto, de modo que generalmente puede crear un facsímil razonable de cualquier conjunto de datos real con solo unas pocas líneas de código.

Después de importar numpy y pandas, asegúrese de proporcionar una semilla aleatoria si desea que la gente pueda reproducir exactamente sus datos y resultados.

import numpy as np
import pandas as pd

np.random.seed(123)

Un ejemplo de fregadero de cocina

Aquí hay un ejemplo que muestra una variedad de cosas que puede hacer. Se pueden crear todo tipo de marcos de datos de muestra útiles a partir de un subconjunto de esto:

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

Esto 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

Algunas notas:

  1. np.repeaty np.tile(columnas dy e) son muy útiles para crear grupos e índices de forma muy regular. Para 2 columnas, esto se puede usar para duplicar fácilmente las r, expand.grid()pero también es más flexible en cuanto a la capacidad de proporcionar un subconjunto de todas las permutaciones. Sin embargo, para 3 o más columnas, la sintaxis se vuelve difícil de manejar rápidamente.
  2. Para un reemplazo más directo de las r, expand.grid()vea la itertoolssolución en el libro de cocina de pandas o la np.meshgridsolución que se muestra aquí . Aquellos permitirán cualquier número de dimensiones.
  3. Puedes hacer bastante con np.random.choice. Por ejemplo, en la columna g, tenemos una selección aleatoria de 6 fechas de 2011. Además, mediante la configuración replace=False, podemos asegurar que estas fechas son únicas, muy útil si queremos usar esto como un índice con valores únicos.

Datos bursátiles falsos

Además de tomar subconjuntos del código anterior, puede combinar las técnicas para hacer casi cualquier cosa. Por ejemplo, aquí hay un breve ejemplo que combina np.tiley date_rangecrea datos de ticker de muestra para 4 acciones que cubren las mismas fechas:

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

Ahora tenemos un conjunto de datos de muestra con 100 líneas (25 fechas por ticker), pero solo hemos utilizado 4 líneas para hacerlo, lo que facilita la reproducción para todos los demás sin copiar y pegar 100 líneas de código. A continuación, puede mostrar subconjuntos de datos si ayuda a explicar su pregunta:

>>> 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 de un contestador

Mi mejor consejo para hacer preguntas sería jugar con la psicología de las personas que responden preguntas. Siendo una de esas personas, puedo dar una idea de por qué respondo ciertas preguntas y por qué no respondo a otras.

Motivaciones

Estoy motivado para responder preguntas por varias razones.

  1. Stackoverflow.com ha sido un recurso tremendamente valioso para mí. Quería retribuir.
  2. En mis esfuerzos por retribuir, descubrí que este sitio es un recurso aún más poderoso que antes. Responder preguntas es una experiencia de aprendizaje para mí y me gusta aprender. Lea esta respuesta y comente de otro veterinario . Este tipo de interacción me hace feliz.
  3. ¡Me gustan los puntos!
  4. Ver # 3.
  5. Me gustan los problemas interesantes.

Todas mis intenciones más puras son geniales y todo eso, pero obtengo esa satisfacción si respondo 1 pregunta o 30. Lo que impulsa mis elecciones para qué preguntas responder tiene un gran componente de maximización de puntos.

También dedicaré tiempo a problemas interesantes, pero son pocos y distantes entre sí y no ayudan a quien pregunta y necesita una solución a una pregunta que no es interesante. Lo mejor que puede hacer para que responda una pregunta es servirla en una bandeja para que pueda responderla con el menor esfuerzo posible. Si estoy viendo dos preguntas y una tiene código, puedo copiar y pegar para crear todas las variables que necesito ... ¡Estoy tomando esa! Volveré al otro si tengo tiempo, tal vez.

Consejo principal

Facilítelo a las personas que responden preguntas.

  • Proporcione código que cree las variables necesarias.
  • Minimiza ese código. Si mis ojos se ponen vidriosos mientras miro la publicación, paso a la siguiente pregunta o vuelvo a cualquier otra cosa que esté haciendo.
  • Piense en lo que está preguntando y sea específico. Queremos ver lo que ha hecho porque los lenguajes naturales (inglés) son inexactos y confusos. Los ejemplos de código de lo que ha probado ayudan a resolver las inconsistencias en una descripción en lenguaje natural.
  • POR FAVOR muestre lo que espera !!! Tengo que sentarme y probar cosas. Casi nunca sé la respuesta a una pregunta sin probar algunas cosas. Si no veo un ejemplo de lo que está buscando, podría pasar la pregunta porque no tengo ganas de adivinar.

Tu reputación es más que solo tu reputación.

Me gustan los puntos (lo mencioné arriba). Pero esos puntos no son realmente mi reputación. Mi reputación real es una fusión de lo que otros en el sitio piensan de mí. Me esfuerzo por ser justo y honesto y espero que otros puedan verlo. Lo que eso significa para quien pregunta es que recordamos el comportamiento de quienes preguntan. Si no selecciona las respuestas y vota a favor de las buenas respuestas, lo recuerdo. Si te comportas de una forma que no me gusta o que me gusta, lo recuerdo. Esto también influye en las preguntas que responderé.


De todos modos, probablemente pueda continuar, pero les perdonaré a todos los que lean esto.

31 Alexander Sep 12 2015 at 14:06

El desafío Uno de los aspectos más desafiantes de responder a las preguntas de SO es el tiempo que lleva recrear el problema (incluidos los datos). Las preguntas que no tienen una forma clara de reproducir los datos tienen menos probabilidades de ser respondidas. Dado que se está tomando el tiempo para escribir una pregunta y tiene un problema con el que desea ayuda, puede ayudarse fácilmente proporcionando datos que otros puedan usar para ayudar a resolver su problema.

Las instrucciones proporcionadas por @Andy para escribir buenas preguntas de Pandas son un excelente lugar para comenzar. Para obtener más información, consulte cómo preguntar y cómo crear ejemplos mínimos, completos y verificables .

Indique claramente su pregunta por adelantado. Después de tomarse el tiempo para escribir su pregunta y cualquier código de muestra, intente leerlo y proporcione un 'Resumen ejecutivo' para su lector que resuma el problema y establezca claramente la pregunta.

Pregunta original :

Tengo estos datos ...

Quiero hacer esto...

Quiero que mi resultado se vea así ...

Sin embargo, cuando intento hacer [esto], aparece el siguiente problema ...

Intenté encontrar soluciones haciendo [esto] y [aquello].

¿Cómo lo soluciono?

Dependiendo de la cantidad de datos, código de muestra y pilas de errores proporcionadas, el lector debe recorrer un largo camino antes de comprender cuál es el problema. Intente repetir su pregunta para que la pregunta en sí esté en la parte superior y luego proporcione los detalles necesarios.

Pregunta revisada :

Qustion: ¿Cómo puedo hacer [esto]?

Intenté encontrar soluciones haciendo [esto] y [aquello].

Cuando intenté hacer [esto], aparece el siguiente problema ...

Me gustaría que mis resultados finales se vieran así ...

Aquí hay un código mínimo que puede reproducir mi problema ...

Y aquí es cómo recrear mis datos de muestra: df = pd.DataFrame({'A': [...], 'B': [...], ...})

¡PROPORCIONE DATOS DE MUESTRA SI ES NECESARIO!

A veces, solo se necesita la cabeza o la cola del DataFrame. También puede utilizar los métodos propuestos por @JohnE para crear conjuntos de datos más grandes que otros puedan reproducir. Usando su ejemplo para generar un DataFrame de 100 filas de precios de acciones:

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

Si estos eran sus datos reales, es posible que desee incluir el encabezado y / o el final del marco de datos de la siguiente manera (asegúrese de anonimizar los datos confidenciales):

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

Es posible que también desee proporcionar una descripción del DataFrame (utilizando solo las columnas relevantes). Esto hace que sea más fácil para otros verificar los tipos de datos de cada columna e identificar otros errores comunes (por ejemplo, fechas como cadena frente a fecha y hora64 frente a objeto):

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: Si su DataFrame tiene un MultiIndex:

Si su DataFrame tiene un índice múltiple, primero debe restablecer antes de llamar to_dict. Luego debe volver a crear el índice usando 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

Aquí está mi versión de dput, la herramienta R estándar para producir informes reproducibles, para Pandas DataFrames. Probablemente fallará para marcos más complejos, pero parece funcionar en casos simples:

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)

ahora,

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

Tenga en cuenta que esto produce una salida mucho más detallada que DataFrame.to_dict, por ejemplo,

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

para duarriba, pero conserva los tipos de columna . Por ejemplo, en el caso de prueba anterior,

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

porque du.dtypeses uint8y pd.DataFrame(du.to_dict()).dtypeses int64.