关于python:如何制作好的可重复的pandas示例

How to make good reproducible pandas examples

花了相当多的时间观察SO上的r和pandas标签,我得到的印象是pandas问题不太可能包含可重现的数据。这是R社区非常善于鼓励的事情,并且由于这样的指南,新手能够在组合这些示例方面得到一些帮助。能够阅读这些指南并返回可重现数据的人通常会更好地获得他们问题的答案。

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

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

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

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

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


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

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

好的:

  • 包括小*示例DataFrame,作为可运行代码:

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

    或者使用pd.read_clipboard(sep='\s\s+')使其"复制和粘贴",您可以格式化Stack Overflow高亮显示的文本并使用Ctrl + K(或在每行前加四个空格),或者在代码上方和下方放置三个代码,代码不缩进:

    1
    2
    3
    4
    5
    6
    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所以我们有完全相同的框架):< X3>。说,"让我快速编写代码"不是严格的网站主题...

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

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

    解释数字的来源:5是A为1的行的B列之和。

  • 显示您尝试过的代码:

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

    但是说什么是不正确的:A列在索引而不是列中。

  • 确实显示你做了一些研究(搜索文档,搜索StackOverflow),给出一个总结:

    The docstring for sum simply states"Compute sum of group values"

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

    旁白:这里的答案是使用df.groupby('A', as_index=False).sum()

  • 如果你有Timestamp列是相关的,例如你正在重新取样或其他什么,然后明确并将pd.to_datetime应用于他们以获得良好的衡量标准**。

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

    **有时这就是问题本身:它们是字符串。

坏:

  • 不包括MultiIndex,我们无法复制和粘贴(见上文),这是一种与熊猫默认显示的不满,但仍然令人讨厌:

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    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
  • 在提供您想要的结果时,确实提供了解它的内容:

    1
    2
    3
    4
       B
    A  
    1  1
    5  0

    具体说明你如何获得数字(他们是什么)...仔细检查他们是否正确。

  • 如果您的代码抛出错误,请包括整个堆栈跟踪(如果它太嘈杂,可以在以后编辑它)。显示行号(以及它引发的代码的相应行)。

丑陋的:

  • 不链接到我们无权访问的csv(理想情况下根本不链接到外部源...)

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

    我们得到的大部分数据都是专有的:组成类似的数据,看看你是否可以重现问题(一些小问题)。

  • 不要用语言模糊地解释这种情况,就像你有一个"大"的DataFrame,提到一些传递的列名(一定不要提及他们的dtypes)。尝试并详细了解一些在没有看到实际情况的情况下完全没有意义的细节。据推测,没有人会读到本段的末尾。

    散文很糟糕,用小例子就容易了。

  • 在得到实际问题之前,不要包含10+(100+ ??)行数据。

    拜托,我们在日常工作中看到了足够多的东西。我们想要帮助,但不是这样....
    剪切介绍,并在导致您遇到麻烦的步骤中显示相关的DataFrame(或其小版本)。

无论如何,学习Python,NumPy和Pandas都很有趣!


如何创建样本数据集

这主要是通过提供如何创建示例数据帧的示例来扩展@AndyHayden的答案。 Pandas和(特别是)numpy为您提供了各种工具,通常只需几行代码即可创建任何真实数据集的合理传真。

导入numpy和pandas后,如果您希望人们能够准确地再现您的数据和结果,请务必提供随机种子。

1
2
3
4
import numpy as np
import pandas as pd

np.random.seed(123)

厨房水槽的例子

这是一个示例,展示了您可以做的各种事情。可以从以下部分创建各种有用的样本数据帧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)
    })

这会产生:

1
2
3
4
5
6
7
          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解决方案或此处显示的np.meshgrid解决方案。这些将允许任何数量的维度。
  • 你可以用np.random.choice做很多事情。例如,在g列中,我们随机选择了2011年的6个日期。此外,通过设置replace=False,我们可以确保这些日期是唯一的 - 如果我们想将此作为具有唯一值的索引使用,则非常方便。
  • 假股市数据

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

    1
    2
    3
    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行代码。然后,如果有助于解释您的问题,则可以显示数据的子集:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    >>> 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


    回答者的日记

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

    动机

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

  • Stackoverflow.com对我来说是一个非常宝贵的资源。我想回馈。
  • 在我努力回馈的过程中,我发现这个网站比以前更加强大。回答问题对我来说是一次学习经历,我喜欢学习。阅读这个答案和另一位兽医的评论。这种互动让我很开心。
  • 我喜欢点!
  • 见#3。
  • 我喜欢有趣的问题。
  • 所有我最纯粹的意图都是伟大的,但是如果我回答1个问题或者30个问题,我会感到满意。我的选择是什么驱动了哪些问题需要回答,其中包含了点最大化的重要组成部分。

    我也会把时间花在有趣的问题上,但这种情况很少见,并且无助于需要解决一个非有趣问题的提问者。让我回答一个问题的最好办法就是把这个问题放在一个成熟的盘子上,让我尽可能少地回答它。如果我正在看两个问题,一个有代码我可以复制粘贴来创建我需要的所有变量......我正在拿那个!如果我有时间的话,我会回到另一个。

    主要建议

    让人们轻松回答问题。

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

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

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

    无论如何,我可能会继续,但我会饶恕所有真正阅读此内容的人。


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

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

    请事先明确说明您的问题。在花时间撰写您的问题和任何示例代码之后,请尝试阅读并为您的读者提供"执行摘要",其中总结了问题并明确说明了问题。

    原始问题:

    I have this data...

    I want to do this...

    I want my result to look like this...

    However, when I try to do [this], I get the following problem...

    I've tried to find solutions by doing [this] and [that].

    How do I fix it?

    根据提供的数据量,示例代码和错误堆栈,读者需要在了解问题所在之前花很长时间。尝试重述您的问题,以便问题本身位于最前面,然后提供必要的详细信息。

    修订问题:

    Qustion: How can I do [this]?

    I've tried to find solutions by doing [this] and [that].

    When I've tried to do [this], I get the following problem...

    I'd like my final results to look like this...

    Here is some minimal code that can reproduce my problem...

    And here is how to recreate my sample data:
    df = pd.DataFrame({'A': [...], 'B': [...], ...})

    如果需要提供样本数据!

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

    1
    2
    3
    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) })

    如果这是您的实际数据,您可能只想包括数据帧的头部和/或尾部,如下所示(确保匿名化任何敏感数据):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    >>> 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对象):

    1
    2
    3
    4
    5
    6
    7
    8
    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重新创建索引:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    # 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

    这是我的dput版本 - 用于生成可重现报告的标准R工具 - 用于Pandas DataFrame
    对于更复杂的帧,它可能会失败,但它似乎在简单的情况下完成工作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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)

    现在,

    1
    2
    3
    4
    5
    6
    7
    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))

    VS

    {'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,但它保留了列类型。
    例如,在上述测试案例中,

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

    因为du.dtypesuint8pd.DataFrame(du.to_dict()).dtypesint64