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

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

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

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

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

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

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

Ответы

Ответ 1

Примечание: идеи здесь довольно общие для Qaru, на самом деле вопросы.

Отказ от ответственности: написание хорошего вопроса является трудным.

Хорошее:

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

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

    или сделайте его "копируемым и вставляемым" с помощью pd.read_clipboard(sep='\s\s+'), вы можете отформатировать текст для подсветки Qaru и использовать 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 строк citation needed, и я уверен, что я могу сделать это в 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 находится в индексе, а не в столбце.

  • показать, что вы провели какое-то исследование (поиск в документах, поиск в Qaru), приведите краткое изложение:

    Строка документа для суммы просто заявляет "Вычислить сумму значений группы"

    The groupby docs don't give any examples for this.

    В сторону: ответ здесь заключается в использовании 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, который "большой", упомяните некоторые имена столбцов попутно (не забудьте упомянуть их dtypes). Постарайтесь подробно рассказать о чем-то, что совершенно бессмысленно, не видя реального контекста. Предположительно никто даже не собирается читать до конца этого абзаца.

    Очерки плохие, легче с небольшими примерами.

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

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

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

Ответ 2

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

Это главным образом для расширения ответа @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 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

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

  • np.repeat и np.tile (столбцы d и e) очень полезны для создания групп и индексов очень обычным способом. Для 2 столбцов это можно использовать для простого дублирования r expand.grid(), но также более гибко в возможности предоставить подмножество всех перестановок. Однако для 3 или более столбцов синтаксис быстро становится громоздким.
  • Для более прямой замены для r expand.grid() см. решение itertools в pandas cookbook или в решении np.meshgrid показан здесь. Они позволят любое количество измерений.
  • Вы можете сделать немного с 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

Ответ 3

Дневник ответчика

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

Мотивы

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

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

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

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

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

Помогите людям ответить на вопросы.

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

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

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


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

Ответ 4

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

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

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

Оригинальный вопрос:

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

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

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

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

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

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

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

Пересмотренный вопрос:

Qustion: Как я могу это сделать?

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

Когда я попытался сделать это, я получаю следующую проблему...

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

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

И вот как воссоздать мои примерные данные:     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 (используя только соответствующие столбцы). Это облегчает для других проверку типов данных каждого столбца и выявление других распространенных ошибок (например, даты как строка vs. datetime64 vs. object):

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 имеет мультииндекс, вы должны сначала reset перед вызовом 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

Ответ 5

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

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 (начало = 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.