如何制作好的可重复熊猫示例

How to make good reproducible pandas examples

提问人:Marius 提问时间:11/21/2013 最后编辑:wjandreaMarius 更新时间:10/23/2023 访问量:51808

问:

在花了相当多的时间观察 标签后,我得到的印象是问题不太可能包含可重复的数据。这是 R 社区一直非常鼓励的事情,并且由于像这样的指南,新手能够在将这些示例放在一起时获得一些帮助。能够阅读这些指南并获得可重复数据的人通常会有更好的运气来获得问题的答案。pandas

我们怎样才能为问题创建良好的可重复示例?简单的数据帧可以放在一起,例如:pandas

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

但许多示例数据集需要更复杂的结构,例如:

  • datetime指数或数据
  • 多个分类变量(是否有与 R 函数等价的函数,它生成某些给定变量的所有可能组合?expand.grid()
  • 多索引数据

对于难以使用几行代码模拟的数据集,是否有与 R 等效的数据集,它允许您生成可复制粘贴的代码来重新生成数据结构?dput()

R 熊猫 Python

评论

8赞 Andy Hayden 11/21/2013
如果复制打印输出,则大多数情况下应答者可以使用 read_clipboard()...除了 MultiIndex :s。话虽如此,dict 是很好的补充
8赞 Paul H 11/21/2013
除了Andy说的,我觉得复制粘贴,哪里是一些合理的数字就是一个不错的选择。奖金 +1 用于在输出中添加漂亮的换行符。对于时间戳,通常只需添加到代码的顶部即可。df.head(N).to_dict()Nfrom pandas import Timestamp

答:

481赞 Andy Hayden 11/23/2013 #1

注意:这里的大多数想法对于 Stack Overflow 来说都是非常通用的,实际上是一般的问题。请参阅最小的、可重现的示例或简短的、独立的、正确的示例

免责声明:写一个好问题很难。

优点:

  • 请包含一个小示例 DataFrame,作为可运行的代码:

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

    或使用 使其“可复制和粘贴”。自己测试一下。pd.read_clipboard(sep='\s\s+')

    In [2]: df
    Out[2]:
       A  B
    0  1  2
    1  1  3
    2  4  6
    
    • 您可以通过突出显示并使用 +(或在每行前面添加四个空格)来设置 Stack Overflow 的文本格式,或者在代码的上方和下方放置三个反引号 ('''),而不缩进代码。CtrlK

    • 我真的是说小。绝大多数示例 DataFrame 可能少于 6 行,[需要引用]我敢打赌我可以在 5 行内完成。你能重现错误吗?如果没有,请摆弄一下,看看是否可以制作一个小的 DataFrame,以展示您面临的问题。df = df.head()

      但是每个规则都有一个例外,最明显的例外是性能问题(在这种情况下,一定要使用 %timeit可能的 %prun 来分析你的代码),你应该在其中生成:

      df = pd.DataFrame(np.random.randn(100000000, 10))
      

      考虑使用,这样我们就有了完全相同的框架。话虽如此,“为我快速编写此代码”并不是该网站的严格主题。np.random.seed

    • 为了获取可运行的代码,df.to_dict通常很有用,针对不同的情况有不同的选项。在上面的示例中,我本可以从 中获取数据和列。orientdf.to_dict('split')

  • 写出你想要的结果(与上面类似)

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

    解释这些数字的来源:

    5 是 A 为 1 的行的 B 列的总和。

  • 请显示您尝试过的代码

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

    但要说出不正确的地方:

    A 列位于索引中,而不是在列中。

  • 请证明你已经做了一些研究(搜索文档搜索 Stack Overflow),并给出一个总结:

    sum 的文档字符串仅声明“计算组值的总和”

    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,我们无法复制和粘贴它(见上文)。这是对 Pandas 默认显示的不满,但仍然很烦人:

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

    正确的方法是在set_index调用中包含一个普通的 DataFrame:

    In [12]: df = pd.DataFrame([[1, 2, 3], [1, 2, 6]], columns=['A', 'B', 'C'])
    
    In [13]: df = df.set_index(['A', 'B'])
    
    In [14]: df
    Out[14]:
         C
    A B
    1 2  3
      2  6
    
  • 在给出你想要的结果时,一定要提供对它的洞察力。

       B
    A
    1  1
    5  0
    

    具体说明您是如何获得这些数字的(它们是什么)......仔细检查它们是否正确。

  • 如果代码引发错误,请包含整个堆栈跟踪。如果太吵,可以稍后编辑掉。显示它所针对的代码的行号和相应的行。

  • Pandas 2.0 引入了许多更改,在此之前的 Pandas 1.0 中引入了许多更改,因此,如果您收到意外的输出,请包含以下版本:

    pd.__version__
    

    在这一点上,您可能还希望包括 Python 版本、您的操作系统和任何其他库。您可以使用 或 session_info包(显示加载的库和 Jupyter/IPython 环境)。pd.show_versions()

丑陋的:

  • 不要链接到我们无权访问的 CSV 文件(理想情况下,不要链接到外部源)。

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

    大多数数据都是专有的,我们明白这一点。编造类似的数据,看看是否可以重现问题(一些小问题)。

  • 不要用文字模糊地解释情况,比如你有一个“大”的 DataFrame,顺便提一些列名(一定不要提及它们的 dtypes)。试着在不看到实际上下文的情况下,对一些完全没有意义的事情进行大量的细节。想必没有人会读到这一段的结尾。

    论文很糟糕;用小例子更容易。

  • 在回答您的实际问题之前,不要包括 10+ (100+??) 行数据。

    拜托,我们在日常工作中看到了足够多的这种情况。我们想帮忙,但不是这样的...... 剪掉介绍,只在给您带来麻烦的步骤中显示相关的 DataFrame(或它们的小版本)。

评论

56赞 zelusp 4/14/2016
+1 小费。当我发布需要特殊但易于共享的数据帧的 SO 问题时,就像这个一样,我在 excel 中构建它,将其复制到我的剪贴板,然后指示 SOers 做同样的事情。节省了很多时间!pd.read_clipboard(sep='\s\s+')
2赞 user5359531 12/10/2016
如果您在远程服务器上使用 Python,则该建议似乎不起作用,因为远程服务器是许多大型数据集所在的位置。pd.read_clipboard(sep='\s\s+')
2赞 MarianD 12/27/2018
为什么,而不是更简单(使用默认值)?第一个至少需要 2 个空格字符,如果只有 1 个,可能会导致问题(例如,在@JohnE的答案中看到这样的字符)。pd.read_clipboard(sep='\s\s+')pd.read_clipboard()‘s+’
5赞 Andy Hayden 12/28/2018
@MarianD \s\s+ 如此受欢迎的原因是,列名中经常有一个,但多个更罕见,而 pandas 输出很好地在列之间放置了至少两个。由于这仅适用于玩具/小型数据集,因此它非常强大/大多数情况下。注意:制表符分隔是另一回事,尽管 stackoverflow 用空格替换制表符,但如果您有 tsv,则只需使用 \t。
4赞 U13-Forward 6/10/2019
呃,我总是使用 ,当有空格时,我会: :Ppd.read_clipboard()pd.read_clipboard(sep='\s+{2,}', engine='python')
99赞 JohnE 5/24/2015 #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'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和 (列和 ) 对于以非常常规的方式创建组和索引非常有用。对于 2 列,这可用于轻松复制 r,但在提供所有排列的子集方面也更灵活。但是,对于 3 列或更多列,语法很快就会变得笨拙。np.tiledeexpand.grid()
  2. 有关 R 的更直接替代品,请参阅 pandas 说明书中的解决方案或此处显示的解决方案。这些将允许任意数量的维度。expand.grid()itertoolsnp.meshgrid
  3. 你可以用 做很多事情。例如,在列中,我们随机选择了 2011 年的六个日期。此外,通过设置,我们可以确保这些日期是唯一的 - 如果我们想将其用作具有唯一值的索引,这将非常方便。np.random.choicegreplace=False

虚假的股票市场数据

除了获取上述代码的子集之外,您还可以进一步组合这些技术来执行几乎任何操作。例如,下面是一个简短的示例,它组合并创建了涵盖相同日期的 4 只股票的样本股票行情数据:np.tiledate_range

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

评论

4赞 Marius 5/25/2015
很好的答案。写完这个问题后,我实际上确实写了一个非常简短、简单的实现,它包含在 pandas 食谱中,您也可以将其包含在您的答案中。您的答案显示了如何创建比我的函数可以处理的更复杂的数据集,这很棒。expand.grid()expand_grid()
46赞 Alexander 9/12/2015 #3

挑战回答 SO 问题最具挑战性的方面之一是重现问题(包括数据)所需的时间。没有明确方法重现数据的问题不太可能得到回答。鉴于您正在花时间写一个问题,并且您有一个需要帮助的问题,您可以通过提供其他人可以使用的数据来帮助解决您的问题,从而轻松地帮助自己。

@Andy 提供的编写好的 Pandas 问题的说明是一个很好的起点。有关更多信息,请参阅如何提问以及如何创建最小、完整和可验证示例

请提前明确说明您的问题。在花时间写下你的问题和任何示例代码后,试着阅读它,并为你的读者提供一个“执行摘要”,总结问题并清楚地说明问题。

原题

我有这个数据...

我想做这个...

我希望我的结果看起来像这样......

但是,当我尝试这样做时,我遇到了以下问题......

我试图通过做[这个]和[那个]来找到解决方案。

我该如何解决?

根据提供的数据量、示例代码和错误堆栈,读者需要走很长的路才能理解问题所在。尝试重述您的问题,使问题本身位于顶部,然后提供必要的详细信息。

修订问题

来源:我该怎么做?

我试图通过做[这个]和[那个]来找到解决方案。

当我尝试这样做时,我遇到了以下问题......

我希望我的最终结果看起来像这样......

这是一些可以重现我问题的最小代码......

以下是重新创建示例数据的方法:df = pd.DataFrame({'A': [...], 'B': [...], ...})

如果需要,请提供示例数据!!

有时只需要 DataFrame 的头部或尾部。您还可以使用 @JohnE 建议的方法创建可由其他人复制的更大数据集。使用他的示例生成 100 行的股票价格数据帧:

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_dictset_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
69赞 piRSquared 7/20/2016 #4

答辩人日记

对于提问,我最好的建议是利用回答问题的人的心理。作为这些人中的一员,我可以深入了解为什么我回答某些问题以及为什么我不回答其他问题。

动机

出于以下几个原因,我有动力回答问题

  1. Stackoverflow.com 对我来说是一个非常宝贵的资源。我想回馈社会。
  2. 在我回馈社会的努力中,我发现这个网站是一个比以前更强大的资源。回答问题对我来说是一种学习经历,我喜欢学习。阅读此答案和另一位兽医的评论。这种互动让我很开心。
  3. 我喜欢积分!
  4. 见#3。
  5. 我喜欢有趣的问题。

我所有最纯粹的意图都是伟大的,但如果我回答 1 个问题或 30 个问题,我就会感到满意。是什么驱使我选择回答哪些问题,这与分数最大化有很大关系。

我也会花时间在有趣的问题上,但这很少,而且对需要解决不有趣问题的提问者没有帮助。让我回答问题的最好办法是把这个问题放在一个成熟的盘子里,让我尽可能少地回答它。如果我正在查看两个问题,其中一个有代码,我可以复制粘贴以创建我需要的所有变量......我拿那个!如果我有时间,也许我会回到另一个。

主要建议

让回答问题的人变得容易。

  • 提供用于创建所需变量的代码。
  • 最小化该代码。如果我在看帖子时眼睛呆滞,那么我就会继续下一个问题,或者回到我正在做的其他事情上。
  • 想想你在问什么,要具体。我们想看看你做了什么,因为自然语言(英语)是不准确和令人困惑的。您尝试过的代码示例有助于解决自然语言描述中的不一致问题。
  • 请展示您的期望!!我必须坐下来尝试一下。如果不尝试一些事情,我几乎永远不知道问题的答案。如果我没有看到你要找的例子,我可能会把这个问题抛给别人,因为我不想猜测。

您的声誉不仅仅是您的声誉。

我喜欢积分(我在上面提到过)。但这些观点并不是我的名声。我的真实声誉是网站上其他人对我的看法的融合。我努力做到公平和诚实,我希望其他人能看到这一点。对于提问者来说,这意味着,我们记住提问者的行为。我记得,如果你不选择答案并点赞好的答案。如果你以我不喜欢的方式或我喜欢的方式行事,我会记住。这也影响了我将回答哪些问题。


无论如何,我可能会继续说下去,但我会饶恕所有真正读过这篇文章的人。

24赞 sds 12/17/2016 #5

这是我的 dput 版本——用于生成可重现报告的标准 R 工具——用于 Pandas s。 对于更复杂的帧,它可能会失败,但它似乎在简单的情况下可以完成这项工作: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.dtypesuint8pd.DataFrame(du.to_dict()).dtypesint64

评论

0赞 Paul H 2/24/2017
它更清楚,尽管我承认我不明白为什么我想使用它to_dict
5赞 sds 2/24/2017
因为它保留了列类型。更具体地说,.du.equals(eval(dput(df)))
0赞 butterflyknife 12/15/2021
我喜欢这个。我有一个带有插值字符串的更现代的版本,它也用换行符分解了输出:def dput(x): indent = " " if isinstance(x,pd.Series): return f"pd.Series({list(x)},dtype='{x.dtype}',index=pd.{x.index}),\r\n" if isinstance(x,pd.DataFrame): temp = "pd.DataFrame({\r\n" + indent temp += indent.join([ f"'{c}': {dput(x[c])}" for c in x.columns]) temp += (f"}}, index=pd.{x.index})") return temp.replace("nan", "float(\'NaN\')") raise NotImplementedError("dput",type(x),x)