如何制作良好的可重现 pandas 示例

Posted

技术标签:

【中文标题】如何制作良好的可重现 pandas 示例【英文标题】:How to make good reproducible pandas examples 【发布时间】:2013-12-05 05:45:02 【问题描述】:

花了相当多的时间查看 SO 上的 r 和 pandas 标签,我得到的印象是 pandas 问题不太可能包含可重复的数据。这是 R 社区一直非常鼓励的事情,并且感谢 this 之类的指南,新手能够在整理这些示例时获得一些帮助。能够阅读这些指南并返回可重复数据的人通常会更幸运地获得问题的答案。

我们如何为pandas 问题创建良好的可重复示例?可以将简单的数据框放在一起,例如:

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

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

datetime 索引或数据 多个分类变量(是否有等效于 R 的 expand.grid() 函数,它产生某些给定变量的所有可能组合?) MultiIndex 或面板数据

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

【问题讨论】:

如果你复制打印的输出,大多数时候回答者可以使用 read_clipboard()... 除了 MultiIndex :s。话虽如此,dict是很好的补充 除了安迪所说的,我认为复制粘贴df.head(N).to_dict(),其中N 是一个合理的数字是一个不错的方法。为输出添加漂亮换行符的奖励 +1。对于时间戳,您通常只需将 from pandas import Timestamp 添加到代码顶部。 【参考方案1】:

注意:这里的想法对于 Stack Overflow 来说是非常通用的,确实是 questions。

免责声明:写出好问题很难

善:

包含 small* 示例 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+')

* 我的意思是 ,绝大多数示例 DataFrame 可能少于 6 行需要引用我敢打赌我可以分 5 行完成。 你能用df = df.head() 重现错误吗?如果没有,请四处看看是否可以制作一个小型 DataFrame 来展示您所面临的问题。

* 每个规则都有一个例外,很明显的一个是针对性能问题 (in which case definitely use %timeit and possibly %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 是 A 为 1 的行的 B 列的总和。

显示你尝试过的代码

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

但是说一下不正确的地方:A 列在索引中而不是列中。

表明你已经做了一些研究(search the documentation,search Stack Overflow),并给出一个总结:

sum 的文档字符串只是说明“计算组值的总和”

groupby documentation 没有为此提供任何示例。

除此之外:这里的答案是使用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,它我们无法复制和粘贴(见上文)。这是对 Pandas 的默认显示的一种不满,但仍然很烦人:

  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+??) 行数据。

拜托,我们在日常工作中看到的已经够多了。我们想帮忙,但是not like this...。 删掉介绍,只在给你带来麻烦的步骤中显示相关的 DataFrames(或它们的小版本)。

不管怎样,学习 Python、NumPy 和 Pandas 玩得开心!

【讨论】:

+1 为pd.read_clipboard(sep='\s\s+') 提示。当我发布需要特殊但易于共享的数据框的 SO 问题时,like this one 我在 excel 中构建它,将其复制到我的剪贴板,然后指示 SOers 也这样做。节省了很多时间! 如果您在远程服务器上使用 Python,pd.read_clipboard(sep='\s\s+') 的建议似乎不起作用,因为这里有很多大型数据集。 为什么是pd.read_clipboard(sep='\s\s+'),而不是更简单的pd.read_clipboard()(默认‘s+’)?第一个需要至少 2 个空格字符,如果只有 1 个可能会导致问题(例如,在 @JohnE 的 answer 中看到这样的)。 @MarianD \s\s+ 如此受欢迎的原因是通常有一个,例如在列名中,但多个很少见,熊猫输出很好地在列之间放置了至少两个。由于这仅适用于玩具/小型数据集,因此它非常强大/大多数情况。注意:标签分隔将是另一回事,尽管 *** 用空格替换标签,但如果你有一个 tsv,那么只需使用 \t。 呃,我总是使用pd.read_clipboard(),当它们是空格时,我会使用:pd.read_clipboard(sep='\s+2,', engine='python'):P【参考方案2】:

如何创建示例数据集

这主要是通过提供如何创建示例数据框的示例来扩展 AndyHayden's answer。 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

一些注意事项:

    np.repeatnp.tile(列 de)对于以非常常规的方式创建组和索引非常有用。对于 2 列,这可用于轻松复制 r 的 expand.grid(),但在提供所有排列的子集方面也更加灵活。但是,对于 3 列或更多列,语法很快就会变得笨拙。 要更直接地替换 R 的 expand.grid(),请参见 pandas cookbook 中的 itertools 解决方案或 here 中显示的 np.meshgrid 解决方案。这些将允许任意数量的维度。 您可以使用np.random.choice 做很多事情。例如,在g 列中,我们随机选择了 2011 年的六个日期。此外,通过设置replace=False,我们可以确保这些日期是唯一的——如果我们想将其用作具有唯一值的索引,这非常方便.

虚假股市数据

除了获取上述代码的子集之外,您还可以进一步结合这些技术来做任何事情。例如,下面是一个简短的示例,它结合了 np.tiledate_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

【讨论】:

很好的答案。在写完这个问题之后,我实际上确实写了一个非常简短的expand.grid() 实现,它包含在pandas cookbook 中,您也可以将其包含在您的答案中。您的回答显示了如何创建比我的 expand_grid() 函数可以处理的更复杂的数据集,这很棒。 这是一个非常有用的示例,我将使用它作为示例的基础。非常感谢!【参考方案3】:

答题者日记

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

动机

我有动力回答问题有几个原因

    ***.com 对我来说是非常宝贵的资源。我想回馈。 在我努力回馈的过程中,我发现这个网站是一个比以前更强大的资源。回答问题对我来说是一种学习体验,我喜欢学习。 Read this answer and comment from another vet。这种互动让我很开心。 我喜欢积分! 参见 #3。 我喜欢有趣的问题。

我所有最纯粹的意图都很好,但如果我回答 1 个或 30 个问题,我就会得到满足。是什么驱动我的选择对于要回答的问题有很大的分数最大化。

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

主要建议

让人们更容易回答问题。

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

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

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


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

【讨论】:

【参考方案4】:

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

@Andy 提供的关于编写好的 Pandas 问题的说明是一个很好的起点。更多信息请参考how to ask以及如何创建Minimal, Complete, and Verifiable examples。

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

原问题

我有这个数据...

我想这样做......

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

但是,当我尝试执行 [this] 时,我遇到了以下问题...

我试图通过[this]和[that]找到解决方案。

我该如何解决?

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

修改后的问题

问题:我该怎么做?

我试图通过[this]和[that]找到解决方案。

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

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

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

这里是如何重新创建我的示例数据: 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

【讨论】:

【参考方案5】:

这是我的 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.dtypesuint8pd.DataFrame(du.to_dict()).dtypesint64

【讨论】:

它更清楚,虽然我承认我不明白为什么我想在to_dict上使用它 因为它保留了列类型。更具体地说,du.equals(eval(dput(df))). 我喜欢这个。我有一个带有插值字符串的更现代的版本,它也用换行符分解输出: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)

以上是关于如何制作良好的可重现 pandas 示例的主要内容,如果未能解决你的问题,请参考以下文章

如何制作一个出色的 R 可重现示例

如何制作一个出色的 R 可重现示例

Pandas Sklearn Pipeline - DataMapper 转换的 CV?

使用 pandas 读取 Excel XML .xls 文件

对 Pandas 列中的一串数字进行排序

在pandas DataFrame中按列计算唯一字符串[关闭]