Как сделать хорошие воспроизводимые примеры панд

Nov 21 2013

Потратив приличное количество времени на просмотр тегов r и pandas на SO, у меня сложилось впечатление, что pandasвопросы с меньшей вероятностью будут содержать воспроизводимые данные. Это то , что R сообщество было очень хорошо о поощрении, и благодаря руководству , как это , новички могут получить некоторую помощь в составлении этих примеров. Люди, которые могут прочитать эти руководства и вернуться с воспроизводимыми данными, часто будут иметь гораздо больше успеха в получении ответов на свои вопросы.

Как мы можем создать хорошие воспроизводимые примеры pandasвопросов? Можно собрать простые фреймы данных, например:

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

Но для многих примеров наборов данных требуется более сложная структура, например:

  • datetime индексы или данные
  • Множественные категориальные переменные (есть ли эквивалент expand.grid()функции R , которая производит все возможные комбинации некоторых заданных переменных?)
  • Данные MultiIndex или Panel

Для наборов данных, которые сложно смоделировать с помощью нескольких строк кода, существует ли эквивалент R, dput()который позволяет вам генерировать копируемый и вставляемый код для восстановления вашей структуры данных?

Ответы

362 AndyHayden Nov 23 2013 at 13:19

Примечание. Идеи здесь довольно общие для Stack Overflow, даже вопросы .

Отказ от ответственности: написать хороший вопрос - ТРУДНО.

Хорошо:

  • включите небольшой * пример DataFrame в виде исполняемого кода:

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

    или сделайте его «копируемым и вставляемым» с помощью pd.read_clipboard(sep='\s\s+'), вы можете отформатировать текст для выделения Stack Overflow и использовать Ctrl+ K(или добавить четыре пробела в каждую строку), или разместить три тильды над и под кодом, оставив код без отступов:

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

    проверь pd.read_clipboard(sep='\s\s+')себя.

    * Я действительно имею в виду маленький , подавляющее большинство примеров DataFrames может содержать менее 6 строк, требующих цитирования , и я уверен, что могу сделать это в 5 строках. Можете ли вы воспроизвести ошибку с помощью df = df.head(), если не поиграться, чтобы увидеть, сможете ли вы создать небольшой DataFrame, который демонстрирует проблему, с которой вы столкнулись.

    * Каждое правило имеет исключение, очевидно , один для проблем с производительностью ( в этом случае , безусловно , использовать% timeit и , возможно , % prun ), где вы должны генерировать (рассмотреть возможность использования np.random.seed поэтому у нас есть точно такой же кадр): df = pd.DataFrame(np.random.randn(100000000, 10)). Сказать, что "сделай этот код быстрым для меня" не совсем по теме сайта ...

  • запишите желаемый результат (как указано выше)

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

    Объясните, откуда взялись числа: 5 - это сумма столбца B для строк, где A равно 1.

  • покажите код, который вы пробовали:

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

    Но скажите, что неправильно: столбец A находится в индексе, а не столбец.

  • покажите, что вы провели некоторое исследование ( поиск в документации , поиск в StackOverflow ), дайте сводку:

    В строке документации для суммы просто указано «Вычислить сумму значений группы».

    В документации groupby нет примеров для этого.

    В сторону: ответ здесь - использовать df.groupby('A', as_index=False).sum().

  • если уместно, что у вас есть столбцы Timestamp, например, вы передискретизируете или что-то в этом роде, тогда явитесь и примените pd.to_datetimeк ним для хорошей оценки **.

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

    ** Иногда проблема заключается в том, что это были струны.

Плохо:

  • не включайте MultiIndex, который мы не можем скопировать и вставить (см. выше), это своего рода недовольство отображением панд по умолчанию, но, тем не менее, раздражает:

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

    Правильный способ - включить обычный DataFrame с 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
    A B   
    1 2  3
      2  6
    
  • дают представление о том, что это такое, когда вы получаете желаемый результат:

       B
    A   
    1  1
    5  0
    

    Будьте конкретны в том, как вы получили числа (какие они) ... дважды проверьте их правильность.

  • Если ваш код выдает ошибку, включите всю трассировку стека (это можно будет отредактировать позже, если это будет слишком шумно). Покажите номер строки (и соответствующую строку вашего кода, против которой он работает).

Уродливый:

  • не связывайтесь с CSV, к которому у нас нет доступа (в идеале вообще не ссылайтесь на внешний источник ...)

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

    Большинство данных являются частными, и мы получаем следующее: составьте аналогичные данные и посмотрите, сможете ли вы воспроизвести проблему (что-то небольшое).

  • не объясняйте ситуацию расплывчато на словах, например, у вас есть DataFrame, который является "большим", упомяните мимоходом некоторые имена столбцов (не забудьте не указывать их типы). Постарайтесь подробно рассказать о чем-то совершенно бессмысленном, не видя реального контекста. Предположительно никто даже не будет дочитать до конца этот абзац.

    Эссе - это плохо, проще с небольшими примерами.

  • не включайте 10+ (100+ ??) строк с данными перед тем, как перейти к вашему актуальному вопросу.

    Пожалуйста, мы видим достаточно этого в нашей повседневной работе. Мы хотим помочь, но не так, как это ... .
    Вырежьте вступление и просто покажите соответствующие DataFrames (или их небольшие версии) на этапе, который вызывает у вас проблемы.

В любом случае, получайте удовольствие от изучения Python, NumPy и Pandas!

77 JohnE May 24 2015 at 21:22

Как создать образцы наборов данных

В основном это делается для того, чтобы расширить ответ @ AndyHayden, предоставив примеры того, как вы можете создавать образцы фреймов данных. Pandas и (особенно) numpy предоставляют вам множество инструментов для этого, так что вы обычно можете создать разумное факсимиле любого реального набора данных с помощью всего нескольких строк кода.

После импорта numpy и pandas обязательно укажите случайное начальное число, если хотите, чтобы люди могли точно воспроизвести ваши данные и результаты.

import numpy as np
import pandas as pd

np.random.seed(123)

Пример кухонной мойки

Вот пример, показывающий, что вы можете делать. Из подмножества этого можно создать всевозможные полезные образцы фреймов данных:

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

Это производит:

          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

Некоторые примечания:

  1. np.repeatи np.tile(столбцы dи e) очень полезны для регулярного создания групп и индексов. Для двух столбцов это можно использовать для простого дублирования r, expand.grid()но он также более гибок в плане возможности предоставить подмножество всех перестановок. Однако для 3 или более столбцов синтаксис быстро становится громоздким.
  2. Для более прямой замены r expand.grid()см. itertoolsРешение в кулинарной книге pandas или np.meshgridрешение, показанное здесь . Это позволит любое количество измерений.
  3. С помощью np.random.choice. Например, в столбце gу нас есть случайный выбор из 6 дат с 2011 года. Кроме того, установив, replace=Falseмы можем гарантировать, что эти даты уникальны - очень удобно, если мы хотим использовать это как индекс с уникальными значениями.

Поддельные данные фондового рынка

Помимо взятия подмножеств приведенного выше кода, вы можете комбинировать методы, чтобы делать что угодно. Например, вот короткий пример, который объединяет np.tileи date_rangeсоздает образцы данных тикера для 4 акций, охватывающих одни и те же даты:

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

Теперь у нас есть образец набора данных из 100 строк (25 дат на тикер), но мы использовали для этого только 4 строки, что упрощает воспроизведение всеми остальными без копирования и вставки 100 строк кода. Затем вы можете отобразить подмножества данных, если это поможет объяснить ваш вопрос:

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

Дневник отвечающего

Мой лучший совет при задании вопросов - играть на психологии людей, отвечающих на вопросы. Как один из таких людей, я могу понять, почему я отвечаю на одни вопросы и почему не отвечаю на другие.

Мотивации

У меня есть мотивация отвечать на вопросы по нескольким причинам

  1. Stackoverflow.com был для меня чрезвычайно ценным ресурсом. Я хотел отдать.
  2. В своих усилиях по возвращению я обнаружил, что этот сайт стал еще более мощным ресурсом, чем раньше. Отвечая на вопросы, я учусь, и мне нравится учиться. Прочтите этот ответ и комментарий другого ветеринара . Такое общение меня радует.
  3. Мне нравятся очки!
  4. См. № 3.
  5. Люблю интересные задачи.

Все мои чистейшие намерения велики и все такое, но я получаю удовлетворение, если отвечаю на 1 или 30 вопросов. Что движет моим выбором, на какие вопросы отвечать, имеет огромный компонент максимизации баллов.

Я также трачу время на интересные проблемы, но их немного, и они не помогут тому, кто задает вопрос, которому нужно решение неинтересного вопроса. Лучший способ заставить меня ответить на вопрос - это подать этот вопрос на блюде, чтобы я мог ответить на него с минимальными усилиями. Если я смотрю на два вопроса и у одного есть код, я могу скопировать вставку, чтобы создать все необходимые мне переменные ... Я беру этот! Я вернусь к другому, если у меня будет время, может быть.

Главный совет

Сделайте так, чтобы люди могли легко отвечать на вопросы.

  • Предоставьте код, который создает необходимые переменные.
  • Сверните этот код. Если мои глаза тускнеют, когда я смотрю на пост, я перехожу к следующему вопросу или возвращаюсь к тому, чем еще занимаюсь.
  • Подумайте, о чем вы спрашиваете, и будьте конкретны. Мы хотим увидеть, что вы сделали, потому что естественный язык (английский) неточен и сбивает с толку. Примеры кода того, что вы пробовали, помогают устранить несоответствия в описании на естественном языке.
  • ПОЖАЛУЙСТА, покажите, чего вы ожидаете !!! Я должен сесть и попробовать. Я почти никогда не узнаю ответ на вопрос, не попробовав кое-что. Если я не увижу пример того, что вы ищете, я могу пропустить вопрос, потому что не хочу гадать.

Ваша репутация - это больше, чем просто ваша репутация.

Мне нравятся очки (я уже говорил об этом выше). Но на самом деле это не моя репутация. Моя настоящая репутация - это результат того, что думают обо мне другие на сайте. Я стремлюсь быть справедливым и честным и надеюсь, что другие это поймут. Для спрашивающего это означает, что мы помним поведение спрашивающего. Если вы не выбираете ответы и не голосуете за хорошие ответы, я помню. Если вы ведете себя так, как мне не нравится, или как я люблю, я вспоминаю. Это также влияет на то, на какие вопросы я отвечу.


В любом случае, я, вероятно, смогу продолжить, но я пощажу всех, кто действительно это читает.

31 Alexander Sep 12 2015 at 14:06

Задача Один из самых сложных аспектов ответа на вопросы SO - время, необходимое для воссоздания проблемы (включая данные). На вопросы, которые не имеют четкого способа воспроизведения данных, ответ будет меньше. Учитывая, что вы нашли время, чтобы написать вопрос, и у вас есть проблема, с которой вы хотели бы помочь, вы можете легко помочь себе, предоставив данные, которые затем могут использовать другие для решения вашей проблемы.

Инструкции по написанию хороших вопросов Pandas, предоставленные @Andy, - отличное место для начала. Для получения дополнительной информации обратитесь к тому, как задать вопрос и как создать минимальные, полные и проверяемые примеры .

Пожалуйста, четко сформулируйте свой вопрос заранее. Потратив время на то, чтобы написать свой вопрос и любой пример кода, попробуйте прочитать его и предоставить читателю «Краткое изложение», в котором кратко изложена проблема и четко сформулирован вопрос.

Исходный вопрос :

У меня есть эти данные ...

Я хочу сделать это...

Я хочу, чтобы мой результат выглядел так ...

Однако, когда я пытаюсь сделать [это], у меня возникает следующая проблема ...

Я пытался найти решения, делая [то] и [то].

Как мне это исправить?

В зависимости от объема данных, примеров кода и стека ошибок читатель должен пройти долгий путь, прежде чем понять, в чем проблема. Попробуйте переформулировать свой вопрос так, чтобы сам вопрос был вверху, а затем укажите необходимые детали.

Исправленный вопрос :

Вопрос: Как я могу [это] сделать?

Я пытался найти решения, делая [то] и [то].

Когда я попытался сделать [это], у меня возникла следующая проблема ...

Я бы хотел, чтобы мои окончательные результаты выглядели так ...

Вот минимальный код, который может воспроизвести мою проблему ...

А вот как воссоздать мои образцы данных: df = pd.DataFrame({'A': [...], 'B': [...], ...})

ПРЕДОСТАВЬТЕ ОБРАЗЕЦ ДАННЫХ, ЕСЛИ НЕОБХОДИМО !!!

Иногда все, что нужно, - это только начало или конец DataFrame. Вы также можете использовать методы, предложенные @JohnE, для создания больших наборов данных, которые могут быть воспроизведены другими. Используя его пример для создания 100-строчного DataFrame цен акций:

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

Если это были ваши фактические данные, вы можете просто включить заголовок и / или хвост фрейма данных следующим образом (обязательно анонимизируйте любые конфиденциальные данные):

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

Вы также можете предоставить описание DataFrame (используя только соответствующие столбцы). Это упрощает другим пользователям проверку типов данных каждого столбца и выявление других распространенных ошибок (например, даты в виде строки по сравнению с datetime64 и объектом):

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)

ПРИМЕЧАНИЕ. Если ваш DataFrame имеет MultiIndex:

Если ваш DataFrame имеет мультииндекс, вы должны сначала сбросить его перед вызовом to_dict. Затем вам нужно воссоздать индекс, используя 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

Вот моя версия dput- стандартного инструмента R для создания воспроизводимых отчетов - для Pandas DataFrame. Вероятно, это не сработает для более сложных кадров, но, похоже, в простых случаях это работает:

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)

сейчас же,

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

Обратите внимание, что это дает гораздо более подробный вывод, чем DataFrame.to_dict, например,

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

против

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

для duвыше, но он сохраняет типы столбцов . Например, в приведенном выше тестовом примере

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

потому что du.dtypesесть uint8и pd.DataFrame(du.to_dict()).dtypesесть int64.