你如何对 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
的上述代码只是一个示例——您必须根据需要进行修改。看起来您的 expected 与 actual 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?
如何在Python Pandas中将MultiIndex Dataframes与权重合并?