你如何对 Python DataFrames 进行单元测试

Posted

技术标签:

【中文标题】你如何对 Python DataFrames 进行单元测试【英文标题】:How do you Unit Test Python DataFrames 【发布时间】:2017-06-10 16:45:06 【问题描述】:

如何对 python 数据框进行单元测试?

我的函数具有作为数据帧的输入和输出。几乎我拥有的每个功能都这样做。现在,如果我想对此进行单元测试,最好的方法是什么?为每个函数创建一个新的数据框(填充了值)似乎有点努力?

有什么资料可以参考吗?你应该为这些函数编写单元测试吗?

【问题讨论】:

【参考方案1】:

虽然 Pandas 的测试函数主要用于内部测试,但 NumPy 包含一组非常有用的测试函数,记录在此:NumPy Test Support。

这些函数比较 NumPy 数组,但您可以使用 values 属性获取作为 Pandas DataFrame 基础的数组。您可以定义一个简单的 DataFrame 并将您的函数返回的内容与您期望的内容进行比较。

您可以使用的一种技术是为多个函数定义一组测试数据。这样,您可以使用Pytest Fixtures 定义该 DataFrame 一次,并在多个测试中使用它。

在资源方面,我发现Testing with NumPy and Pandas 上的这篇文章非常有用。我还在 PyCon Canada 2016 上做了一个关于数据分析测试的简短演讲:Automate Your Data Analysis Testing。

【讨论】:

只是快速更新,Pytest Fixtures 链接已损坏。也许他们移动了页面。这是他们的"About Fixtures" 页面! PyData London 2019,关于该主题的优秀视频。 youtube.com/watch?v=WTj6T0QdHHM&t=4432s【参考方案2】:

您可以使用 pandas 测试功能:

它可以更灵活地以不同方式将您的结果与计算结果进行比较。

例如:

df1=pd.DataFrame('a':[1,2,3,4,5])
df2=pd.DataFrame('a':[6,7,8,9,10])

expected_res=pd.Series([7,9,11,13,15])
pd.testing.assert_series_equal((df1['a']+df2['a']),expected_res,check_names=False)

更多详情请参考link

【讨论】:

【参考方案3】:

我认为创建用于单元测试的小型 DataFrame 并不难?

import pandas as pd
from nose.tools import assert_dict_equal

input = pd.DataFrame.from_dict(
    'field_1': [some, values],
    'field_2': [other, values]
)
expected = 
    'result': [...]

assert_dict_equal(expected, my_func(input).to_dict(), "oops, there's a bug...")

【讨论】:

我的结果也是一个数据框。所以我应该创建另一个数据框?在这种情况下,我不能使用 assert_dict_equal? 是的,这就是为什么我在你的函数的结果上调用了to_dict() - 所以我得到了一个dict,它可以与expected 与建议的nose 方法进行比较。 @rkaleta:是的,确实如此。但是,我的测试失败并出现诸如 AssertionError: 'ins[20 chars]on': ['TF000141124', 'TF000141124', 'TF00014[599 chars ]0.0] != 'ins[20 chars]on': 0: 'TF000141124', 1: 'TF000141124', 2:[716 chars]0.0 Diff 长度为 3078 个字符。将 self.maxDiff 设置为 None 以查看它。 :哎呀,有一个错误...... @CodeGeek123 expected 的上述代码只是一个示例——您必须根据需要进行修改。看起来您的 expectedactual DataFrame 结构不匹配。看起来被测函数返回了一个 3 行 x 1 列的 DataFrame?那么 expected 应该更像expected = <column_name>: <first_row_index_value>: <first_row_value>, <second_row_index_value>: <second_row_value>, <third_row_index_value: <third_row_value>【参考方案4】:

我建议将值以 CSV 格式写入 docstrings(或单独的文件,如果它们很大)并使用 pd.read_csv() 解析它们。您也可以解析来自 CSV 的预期输出并进行比较,或者使用 df.to_csv() 写出 CSV 并进行比较。

【讨论】:

这是个好主意,但是如果你的 DataFrame 有奇怪编码的数据,或者你需要 literal_eval 的数组等,处理 csv 文件可能会很烦人。如果她的代码结构正确输入/预期的 DataFrame 应该相当小,因此易于即时构建?【参考方案5】:

您可以使用snapshottest 并执行以下操作:

def test_something_works(snapshot): # snapshot is a pytest fixture from snapshottest
    data_frame = calc_something_and_return_pandas_dataframe()
    snapshot.assert_match(data_frame.to_csv(index=False), 'some_module_level_unique_name_for_the_snapshot')

这将创建一个快照文件夹,其中包含一个包含 csv 输出的文件,您可以在代码更改时使用 --snapshot-update 更新该文件。

它的工作原理是将data_frame 变量与保存到磁盘的变量进行比较。

可能值得一提的是,您的快照应签入源代码管理。

【讨论】:

【参考方案6】:

如果你使用 pytest,pandasSnapshot 会很有用。

# use with pytest
import pandas as pd
from snapshottest_ext.dataframe import PandasSnapshot

def test_format(snapshot):
    df = pd.DataFrame([['a', 'b'], ['c', 'd']],
                      columns=['col 1', 'col 2'])
    snapshot.assert_match(PandasSnapshot(df))

一个很大的缺点是快照不再可读。 (将内容存储为 csv 可读性更高,但存在问题。

PS:我是pytest快照扩展的作者。

【讨论】:

【参考方案7】:

frame-fixtures Python 包(我是该包的作者)旨在简化“创建新数据框(填充值)”以进行单元或性能测试。

例如,如果您想针对具有数字索引的浮点数和字符串的 DataFrame 进行测试,您可以使用紧凑的字符串声明来生成 DataFrame。

>>> ff.Fixture.to_frame('i(I,int)|v(float,str)|s(4,2)').to_pandas()
              0     1
 34715  1930.40  zaji
-3648  -1760.34  zJnC
 91301  1857.34  zDdR
 30205  1699.34  zuVU

>>> ff.Fixture.to_frame('i(I,int)|v(float,str)|s(8,3)').to_pandas()
               0     1        2
 34715   1930.40  zaji   694.30
-3648   -1760.34  zJnC   -72.96
 91301   1857.34  zDdR  1826.02
 30205   1699.34  zuVU   604.10
 54020    268.96  zKka  1080.40
 129017  3511.58  zJXD  2580.34
 35021   1175.36  zPAQ   700.42
 166924  2925.68  zyps  3338.48

【讨论】:

【参考方案8】:

Pandas 内置了测试功能,但我发现输出的解析并不容易,因此我创建了一个名为 beavis 的开源项目,其功能可以输出更易于人类阅读的错误消息。

以下是其中一种内置测试方法的示例:

df = pd.DataFrame("col1": [1042, 2, 9, 6], "col2": [5, 2, 7, 6])
pd.testing.assert_series_equal(df["col1"], df["col2"])

这是错误信息:


>   ???
E   AssertionError: Series are different
E
E   Series values are different (50.0 %)
E   [index]: [0, 1, 2, 3]
E   [left]:  [1042, 2, 9, 6]
E   [right]: [5, 2, 7, 6]

由于输出未对齐,因此不太容易查看哪些行不匹配。

以下是使用 beavis 编写相同测试的方法。

import beavis

beavis.assert_pd_column_equality(df, "col1", "col2")

这将为您提供以下可读的错误消息:

内置的assert_frame_equal 也不会给出可读的错误信息。以下是如何将 DataFrame 相等性与 beavis 进行比较。

df1 = pd.DataFrame('col1': [1, 2], 'col2': [3, 4])
df2 = pd.DataFrame('col1': [5, 2], 'col2': [3, 4])
beavis.assert_pd_equality(df1, df2)

【讨论】:

以上是关于你如何对 Python DataFrames 进行单元测试的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Apache Spark Dataframes (Python) 执行 Switch 语句

Python Pandas - 如何在具有不同级别数的索引上加入 DataFrames?

Julia DataFrames - 如何进行一次热编码?

如何在Python Pandas中将MultiIndex Dataframes与权重合并?

扁平化(不规则)Python 中关于 Pandas Dataframes 的列表列表

python Spark Dataframes的备忘单(使用Python)