Cómo hacer buenos ejemplos de pandas reproducibles

Después de haber pasado una cantidad decente de tiempo viendo las tags r y pandas en SO, la impresión que tengo es que es menos probable que las preguntas de los pandas contengan datos reproducibles. Esto es algo que la comunidad R ha sido muy buena alentando, y gracias a guías como esta , los recién llegados pueden obtener ayuda para reunir estos ejemplos. Las personas que pueden leer estas guías y regresar con datos reproducibles a menudo tendrán mucha mejor suerte al obtener respuestas a sus preguntas.

¿Cómo podemos crear buenos ejemplos reproducibles para las preguntas de los pandas ? Los marcos de datos simples se pueden juntar, 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:

  • Índices o datos de datetime
  • Múltiples variables categóricas (¿existe un equivalente a la función expand.grid() R, que produce todas las combinaciones posibles de algunas variables dadas?)
  • MultiIndex o datos del panel

Para los conjuntos de datos que son difíciles de imitar usando unas pocas líneas de código, ¿hay un equivalente al dput() R que le permite generar código que se puede copiar y regenerar para regenerar su estructura de datos?

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

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

El bueno:

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

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

    o haga que se pueda “copiar y pegar” usando pd.read_clipboard(sep='\s\s+') , puede formatear el texto para el resaltado de StackOverflow y usar Ctrl + K (o añadir cuatro espacios a cada línea):

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

    pruebe pd.read_clipboard(sep='\s\s+') usted mismo.

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

    * Todas las reglas tienen una excepción, la más obvia es para los problemas de rendimiento ( en cuyo caso definitivamente use% timeit y posiblemente% prun ), donde debería generar (considere usar np.random.seed para que tengamos el mismo marco): df = pd.DataFrame(np.random.randn(100000000, 10)) . Decir que “haz este código rápido para mí” no es estrictamente un tema para el sitio …

  • escriba el resultado que desea (similar a lo anterior)

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

    Explica de qué provienen los números: el 5 es la sum 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 diga lo que es incorrecto: la columna A está en el índice en lugar de una columna.

  • muestra que has investigado ( busca en los documentos , busca en StackOverflow ), da un resumen:

    La cadena de documentos para la sum simplemente dice “Calcular la sum de los valores de 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í, entonces sea explícito y aplique pd.to_datetime a ellos para una buena medida **.

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

    ** A veces este es el problema en sí: eran cuerdas.

El malo:

  • no incluya un MultiIndex, que no podemos copiar y pegar (ver arriba), esto es una especie de queja con la visualización predeterminada de pandas, pero aún así molesta:

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

    La forma correcta es incluir un DataFrame ordinario con una llamada a set_index :

     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 AB 1 2 3 2 6 
  • proporcione información sobre qué es cuando da 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 stack (esto puede ser editado más tarde si es demasiado ruidoso). Muestra el número de línea (y la línea correspondiente de tu código contra el que está subiendo).

El feo:

  • no se vincula a un csv al que no tenemos acceso (idealmente no se vincula 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 de propiedad exclusiva , 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 al pasar (asegúrese de no mencionar sus dtypes). Intente entrar en muchos detalles sobre algo que no tiene ningún significado sin ver el contexto real. Presumiblemente, nadie va a leer 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 datos antes de llegar a su pregunta real.

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

De todos modos, ¡diviértanse aprendiendo python, numpy y pandas!

Cómo crear conjuntos de datos de muestra

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

Después de importar números 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 puedes hacer. Se podría 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:

  abcdefg 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.repeat y np.tile (columnas d y e ) son muy útiles para crear grupos e índices de una manera muy regular. Para 2 columnas, esto se puede usar para duplicar fácilmente el expand.grid() r, pero también es más flexible en la capacidad de proporcionar un subconjunto de todas las permutaciones. Sin embargo, para 3 o más columnas, la syntax se vuelve rápidamente inmanejable.
  2. Para obtener un reemplazo más directo de r’s expand.grid() consulte la solución de itertools en el libro de cocina pandas o la solución np.meshgrid que se muestra aquí . Eso permitirá 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 a partir de 2011. Además, al establecer replace=False , podemos asegurar que estas fechas son únicas; muy útil si queremos usar esto como un índice con valores únicos.

Falsos datos del mercado de valores.

Además de tomar subconjuntos del código anterior, puede combinar aún más las técnicas para hacer casi cualquier cosa. Por ejemplo, aquí hay un breve ejemplo que combina np.tile y date_range para crear 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 de todos los demás sin copiar y pegar 100 líneas de código. Luego puede mostrar los subconjuntos de los datos si le 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 

Diario de un respondedor

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

Motivaciones

Estoy motivado a responder preguntas por varias razones.

  1. Stackoverflow.com ha sido un recurso tremendamente valioso para mí. Yo quería devolverte.
  2. En mis esfuerzos por devolver, encontré 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 el comentario de otro veterinario . Este tipo de interacción me hace feliz.
  3. ¡Me gustan los puntos!
  4. Vea el # 3.
  5. Me gustan los problemas interesantes.

Todas mis intenciones más puras son geniales y todas, pero me satisface si respondo una o 30 preguntas. Lo que impulsa mis elecciones para qué preguntas responder tiene un componente enorme de la maximización de puntos.

También dedicaré tiempo a problemas interesantes, pero eso es muy poco y no ayuda a un autor de la pregunta que necesita una solución a una pregunta no interesante. Lo mejor que puedes hacer para que responda una pregunta es responder esa pregunta en un plato maduro para que la responda con el menor esfuerzo posible. Si estoy viendo dos preguntas y una tiene un código, puedo copiar y pegar para crear todas las variables que necesito … ¡Estoy tomando esa! Regresaré al otro si tengo tiempo, tal vez.

Consejo principal

Que sea fácil para las personas que responden preguntas.

  • Proporcionar código que crea las variables que son necesarias.
  • Minimiza ese código. Si mis ojos se ponen vidriosos cuando miro la publicación, pasaré a la siguiente pregunta o volveré a lo que sea que esté haciendo.
  • Piensa en lo que estás pidiendo y sé específico. Queremos ver lo que has hecho porque los idiomas naturales (inglés) son inexactos y confusos. Los ejemplos de código de lo que ha intentado ayudan a resolver inconsistencias en una descripción de 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 tu reputación.

Me gustan los puntos (mencioné eso arriba). Pero esos puntos no son realmente mi reputación. Mi verdadera reputación es una amalgama de lo que otros en el sitio piensan de mí. Me esfuerzo por ser justo y honesto y espero que otros puedan ver eso. Lo que eso significa para un que pregunta es, recordamos los comportamientos de los que hacen. Si no seleccionas respuestas y promueves buenas respuestas, lo recuerdo. Si te comportas de una manera que no me gusta o de la manera que me gusta, lo recuerdo. Esto también juega en qué preguntas voy a responder.


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

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 le gustaría recibir ayuda, puede ayudarse fácilmente al proporcionar 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 solicitar y cómo crear ejemplos mínimos, completos y verificables .

Por favor, 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 resum el problema e indique claramente la pregunta.

Pregunta original :

Tengo estos datos …

Quiero hacer esto…

Quiero que mi resultado se vea así …

Sin embargo, cuando trato de hacer [esto], me sale el siguiente problema …

He intentado encontrar soluciones haciendo [esto] y [eso].

¿Cómo lo arreglo?

Dependiendo de la cantidad de datos, el código de muestra y las stacks de errores proporcionadas, el lector debe recorrer un largo camino antes de comprender cuál es el problema. Intente reformular 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]?

He intentado encontrar soluciones haciendo [esto] y [eso].

Cuando he intentado hacer [esto], me aparece el siguiente problema …

Me gustaría que mis resultados finales se vean 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': [...], ...})

PROPORCIONAR DATOS DE MUESTRA SI ES NECESARIO

A veces, solo la cabeza o la cola del DataFrame es todo lo que se necesita. También puede usar 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 esta fue su información real, es posible que desee incluir la cabecera y / o la cola de la estructura de datos de la siguiente manera (asegúrese de anonimizar cualquier información confidencial):

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

También puede 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 vs. datetime64 vs. objeto):

 stocks.info()  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 reiniciar antes de llamar a to_dict . Entonces necesitas recrear 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 restre the original MultiIndex. This DataFrame can then be restred. 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 

Aquí está mi versión de dput , la herramienta estándar de R para producir informes reproducibles, para Pandas DataFrame s. Probablemente fallará para marcos más complejos, pero parece que hace el trabajo 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 (inicio = 0, parada = 8 , paso = 1)), ‘foo_2’: pd.Series ([0, 1, 0, 0, 1, 0, 0, 0], dtype = ‘uint8’, index = pd.RangeIndex (inicio = 0, parada = 8, paso = 1)), ‘foo_3’: pd.Series ([0, 0, 1, 0, 0, 0, 1, 0], dtype = ‘uint8’, index = pd.RangeIndex (inicio = 0 , stop = 8, paso = 1)), ‘foo_4’: pd.Series ([0, 0, 0, 1, 0, 0, 0, 0], dtype = ‘uint8’, index = pd.RangeIndex (inicio = 0, stop = 8, paso = 1))}, index = pd.RangeIndex (inicio = 0, stop = 8, paso = 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 du arriba, pero conserva los tipos de columna . Por ejemplo, en el caso de prueba anterior,

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

porque du.dtypes es uint8 y pd.DataFrame(du.to_dict()).dtypes es int64 .