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

221

Потратив приличное количество времени на просмотр обоих и теги на 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()который позволяет вам генерировать копируемый код для регенерации структуры данных?

Marius
источник
8
Если вы копируете вывод печати, в большинстве случаев ответчики могут использовать read_clipboard () ... за исключением MultiIndex: s. Сказав это, Дикт является хорошим дополнением
Энди Хейден
8
В дополнение к тому, что сказал Энди, я думаю, что копирование df.head(N).to_dict(), где Nесть какое-то разумное число, - хороший путь. Бонус +1 за добавление красивых разрывов строк к выводу. Для временных меток вам обычно просто нужно добавить from pandas import Timestampв начало кода.
Пол Н

Ответы:

323

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

Отказ от ответственности: написать хороший вопрос трудно.

Хорошо:

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

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

    или сделав его «копируемым и вставляемым» с помощью pd.read_clipboard(sep='\s\s+'), вы можете отформатировать текст для выделения переполнения стека и использовать 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().

  • если уместно, что у вас есть столбцы меток времени, например, вы производите повторную выборку или что-то в этом роде, тогда будьте явными и применяйте 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!

Энди Хейден
источник
30
+1 за pd.read_clipboard(sep='\s\s+')чаевые. Когда я публикую SO вопросы, которые нуждаются в специальном, но легко распространяемом фрейме данных, как этот, я собираю его в Excel, копирую в буфер обмена, а затем приказываю SO делать то же самое. Экономит так много времени!
zelusp
1
Это pd.read_clipboard(sep='\s\s+')предложение не работает, если вы используете Python на удаленном сервере, где находится множество больших наборов данных.
user5359531
1
Почему pd.read_clipboard(sep='\s\s+')и не проще pd.read_clipboard()(по умолчанию ‘s+’)? Первой необходимости , по крайней мере 2 пробельных символов, которые могут вызвать проблемы , если есть только один (например , см , например в @JohnE «s ответ ).
MarianD
3
@MarianD Причина, по которой \ s \ s + так популярен, заключается в том, что в имени столбца часто встречается один, например, множественное число реже, и вывод панд помещает как минимум два между столбцами. Так как это только для игрушек / небольших наборов данных, это довольно мощный / большинство случаев. Примечание: разделенные вкладки - это отдельная история, хотя stackoverflow заменяет вкладки пробелами, но если у вас есть tsv, просто используйте \ t.
Энди Хейден
3
Тьфу, я всегда использую pd.read_clipboard(), когда они пробелы, я делаю pd.read_clipboard(sep='\s+{2,}', engine='python'):: P
U10-Forward
72

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

Это главным образом для того, чтобы расширить ответ @ 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) очень полезны для регулярного создания групп и индексов. Для 2 столбцов это может быть использовано для простого дублирования r, expand.grid()но также является более гибким в отношении возможности поднабора всех перестановок. Однако для 3 или более столбцов синтаксис быстро становится громоздким.
  2. Для более прямой замены r expand.grid()смотрите itertoolsрешение в поваренной книге панд или 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
Johne
источник
2
Отличный ответ. После написания этого вопроса я действительно написал очень короткую и простую реализацию, expand.grid()которая включена в поваренную книгу панд , вы также можете включить это в свой ответ. Ваш ответ показывает, как создать более сложные наборы данных, чем expand_grid()могла бы обработать моя функция, и это здорово.
Мариус
46

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

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

Мотивы

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

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

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

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

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

Облегчите людям, отвечающим на вопросы.

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

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

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


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

piRSquared
источник
26

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

А вот как воссоздать мои образцы данных: 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
Александр
источник
12

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

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.

ДСН
источник
это более ясно, хотя я признаю, что не понимаю, почему я хотел бы использовать это сноваto_dict
Пол Х
2
Потому что это сохраняет типы столбцов. Более конкретно du.equals(eval(dput(df))).
SdS