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

86

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

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

您能否向我推荐任何材料?是否应该为这些函数编写单元测试?

8个回答

53

虽然Pandas的测试函数主要用于内部测试,但NumPy包含了一组非常有用的测试函数,文档在这里:NumPy Test Support

这些函数比较NumPy数组,但你可以使用values属性获取底层的Pandas DataFrame数据。你可以定义一个简单的DataFrame并将你的函数返回值与期望结果进行比较。

你可以使用一组测试数据来测试多个函数。这样,你可以使用Pytest Fixtures一次性定义该DataFrame,并在多个测试中使用它。

关于资源,我发现这篇文章对于使用NumPy和Pandas进行测试非常有用。我还在PyCon Canada 2016做了一个关于数据分析测试的短暂演讲:自动化你的数据分析测试


快速更新一下,Pytest Fixtures链接已经失效了。也许他们移动了页面。这是他们的"关于Fixtures"页面! - Ryan Streur
2
PyData London 2019,该主题的优秀视频。https://www.youtube.com/watch?v=WTj6T0QdHHM&t=4432s - Cam

34

您可以使用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)

了解更多详情,参考此链接


3
这就是正确的方式。被接受的答案(https://dev59.com/OlgR5IYBdhLWcg3wBZa3#41857520)通常都是无用且已过时的,就像现在一样。公共的`pandas.testing`子包发布的Pandas测试函数绝对是为了在下游测试套件中进行外部测试而设计的。这就是“testing”一词的含义。_ \ 晕倒\ _ - Cecil Curry

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更易读,但存在问题。

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


4

你可以使用 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变量与保存到磁盘的内容来工作。

值得一提的是,您的快照应该被检入源代码管理。


4

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

import pandas as pd
from nose.tools import assert_dict_equal

input_df = pd.DataFrame.from_dict({
    'field_1': [some, values],
    'field_2': [other, values]
})
expected = {
    'result': [...]
}
assert_dict_equal(expected, my_func(input_df).to_dict(), "oops, there's a bug...")

我的结果也是一个数据框,所以我应该创建另一个数据框吗?在这种情况下,我不能使用assert_dict_equal吗? - CodeGeek123
1
是的,这就是为什么我在你的函数结果上调用了 to_dict() - 这样我就可以得到一个 dict,可以使用建议的 nose 方法与 expected 进行比较。 - rtkaleta
@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}}。差异长度为3078个字符。将self.maxDiff设置为None以查看它。:哎呀,有一个bug... - CodeGeek123
@CodeGeek123 上面的代码中 expected 只是一个示例 - 你需要根据自己的需求进行修改。看起来你在 expectedactual DataFrame 结构上存在不匹配的情况。似乎被测试的函数返回的 DataFrame 是 3 行 x 1 列?那么 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>}} - rtkaleta

3
我建议在文档字符串中以CSV格式编写值(如果它们很大,则可以另行存储),然后使用pd.read_csv()进行解析。您也可以从CSV文件中解析预期输出并进行比较,或者使用df.to_csv()将其写出为CSV文件并进行比对。

2
这是一个不错的想法,但如果您的DataFrame具有奇怪的编码或需要进行literal_eval等操作的数组,则处理csv文件可能会很烦人。如果她的代码结构正确,输入/期望的DataFrames应该相当小,因此可以轻松地即时构建? - rtkaleta

3
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")

这将给你以下可读的错误消息:

Columns not equal error

内置的assert_frame_equal也无法提供可读的错误消息。以下是使用beavis比较DataFrame相等性的方法。
df1 = pd.DataFrame({'col1': [1, 2], 'col2': [3, 4]})
df2 = pd.DataFrame({'col1': [5, 2], 'col2': [3, 4]})
beavis.assert_pd_equality(df1, df2)

Beavis DataFrame equality


1

frame-fixtures Python包(我是其中的作者)旨在使单元测试或性能测试中“创建新数据帧(填充值)”变得容易。

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

>>> 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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接