获取数据的统计洞察#

Woodwork 在你的 DataFrames 上提供了方法,让你能够利用 Woodwork 存储的类型信息来更好地理解你的数据。

跟着学习如何在零售数据的 DataFrame 上使用 Woodwork 的统计方法,同时演示函数的全部功能。

[1]:
import numpy as np
import pandas as pd

from woodwork.demo import load_retail

df = load_retail()
df.ww
[1]:
物理类型 逻辑类型 语义标签
order_product_id category 分类 ['index']
order_id category 分类 ['category']
product_id category 分类 ['category']
description string 自然语言 []
quantity int64 整数 ['numeric']
order_date datetime64[ns] 日期时间 ['time_index']
unit_price float64 双精度浮点数 ['numeric']
customer_name category 分类 ['category']
country category 分类 ['category']
total float64 双精度浮点数 ['numeric']
cancelled bool 布尔值 []

DataFrame.ww.describe#

使用 df.ww.describe() 计算 DataFrame 中各列的统计信息,返回结果为 pandas DataFrame 格式,其中包含对每列执行的相关计算。请注意,对于 LatLong 逻辑类型,nan(nan, nan) 值都会计入 nan_count

[2]:
df.ww.describe()
[2]:
order_id product_id description quantity order_date unit_price customer_name country total cancelled
physical_type category category string[python] int64 datetime64[ns] float64 category category float64 bool
logical_type 分类 分类 自然语言 整数 日期时间 双精度浮点数 分类 分类 双精度浮点数 布尔值
semantic_tags {category} {category} {} {numeric} {time_index} {numeric} {category} {category} {numeric} {}
count 401604 401604 401604 401604.0 401604 401604.0 401604 401604 401604.0 401604
nunique 22190 3684 NaN 436.0 20460 620.0 4372 37 3946.0 NaN
nan_count 0 0 0 0 0 0 0 0 0 0
mean NaN NaN NaN 12.183273 2011-07-10 12:08:23.848567552 5.732205 NaN NaN 34.012502 NaN
mode 576339 85123A WHITE HANGING HEART T-LIGHT HOLDER 1 2011-11-14 15:27:00 2.0625 Mary Dalton United Kingdom 24.75 False
std NaN NaN NaN 250.283037 NaN 115.110658 NaN NaN 710.081161 NaN
min NaN NaN NaN -80995 2010-12-01 08:26:00 0.0 NaN NaN -277974.84 NaN
first_quartile NaN NaN NaN 2.0 NaN 2.0625 NaN NaN 7.0125 NaN
second_quartile NaN NaN NaN 5.0 NaN 3.2175 NaN NaN 19.305 NaN
third_quartile NaN NaN NaN 12.0 NaN 6.1875 NaN NaN 32.67 NaN
max NaN NaN NaN 80995 2011-12-09 12:50:00 64300.5 NaN NaN 277974.84 NaN
num_true NaN NaN NaN NaN NaN NaN NaN NaN NaN 8872
num_false NaN NaN NaN NaN NaN NaN NaN NaN NaN 392732

在上面的 dataframe 中有几点需要注意

  • Woodwork 索引 order_product_id 未包含在内

  • 我们根据 Woodwork 的类型系统提供每列的类型信息

  • 任何无法为列计算的统计信息,例如 num_false 应用于 Datetime 列时,都填充为 NaN

  • 除了 nunique 之外,空值不计入任何计算中。

DataFrame.ww.value_counts#

使用 df.ww.value_counts() 计算将 category 作为标准标签的每列中最频繁的值。这会返回一个字典,其中每列都关联着一个按值排序的字典列表。每个字典包含 valuecount

[3]:
df.ww.value_counts()
[3]:
{'order_product_id': [{'value': 401603, 'count': 1},
  {'value': 0, 'count': 1},
  {'value': 1, 'count': 1},
  {'value': 2, 'count': 1},
  {'value': 401564, 'count': 1},
  {'value': 401565, 'count': 1},
  {'value': 401566, 'count': 1},
  {'value': 401567, 'count': 1},
  {'value': 401568, 'count': 1},
  {'value': 401569, 'count': 1}],
 'order_id': [{'value': '576339', 'count': 542},
  {'value': '579196', 'count': 533},
  {'value': '580727', 'count': 529},
  {'value': '578270', 'count': 442},
  {'value': '573576', 'count': 435},
  {'value': '567656', 'count': 421},
  {'value': '567183', 'count': 392},
  {'value': '575607', 'count': 377},
  {'value': '571441', 'count': 364},
  {'value': '570488', 'count': 353}],
 'product_id': [{'value': '85123A', 'count': 2065},
  {'value': '22423', 'count': 1894},
  {'value': '85099B', 'count': 1659},
  {'value': '47566', 'count': 1409},
  {'value': '84879', 'count': 1405},
  {'value': '20725', 'count': 1346},
  {'value': '22720', 'count': 1224},
  {'value': 'POST', 'count': 1196},
  {'value': '22197', 'count': 1110},
  {'value': '23203', 'count': 1108}],
 'customer_name': [{'value': 'Mary Dalton', 'count': 7812},
  {'value': 'Dalton Grant', 'count': 5898},
  {'value': 'Jeremy Woods', 'count': 5128},
  {'value': 'Jasmine Salazar', 'count': 4459},
  {'value': 'James Robinson', 'count': 2759},
  {'value': 'Bryce Stewart', 'count': 2478},
  {'value': 'Vanessa Sanchez', 'count': 2085},
  {'value': 'Laura Church', 'count': 1853},
  {'value': 'Kelly Alvarado', 'count': 1667},
  {'value': 'Ashley Meyer', 'count': 1640}],
 'country': [{'value': 'United Kingdom', 'count': 356728},
  {'value': 'Germany', 'count': 9480},
  {'value': 'France', 'count': 8475},
  {'value': 'EIRE', 'count': 7475},
  {'value': 'Spain', 'count': 2528},
  {'value': 'Netherlands', 'count': 2371},
  {'value': 'Belgium', 'count': 2069},
  {'value': 'Switzerland', 'count': 1877},
  {'value': 'Portugal', 'count': 1471},
  {'value': 'Australia', 'count': 1258}]}

DataFrame.ww.dependence#

df.ww.dependence 计算所有相关列对之间的几种依赖性/相关性度量。某些类型(如字符串)无法计算依赖性。

AB 之间的互信息可以理解为你了解列 B 的值时,能够获得关于列 A 的知识量。 AB 之间的互信息越多,在已知 B 的情况下,A 的不确定性就越小,反之亦然。

皮尔逊相关系数衡量 AB 之间的线性相关性。

[4]:
df.ww.dependence(measures="all", nrows=1000)
/home/docs/checkouts/readthedocs.org/user_builds/feature-labs-inc-datatables/envs/stable/lib/python3.9/site-packages/woodwork/statistics_utils/_get_dependence_dict.py:137: UserWarning: Dropping columns ['order_id', 'customer_name'] to allow mutual information to run faster
  warnings.warn(
[4]:
column_1 column_2 pearson spearman mutual_info max
0 quantity total 0.532443 0.708133 0.217693 0.708133
1 quantity unit_price -0.128447 -0.367468 0.086608 -0.367468
2 unit_price total 0.130113 0.313225 0.104822 0.313225
3 quantity cancelled -0.141777 -0.231845 0.025767 -0.231845
4 total cancelled -0.145310 -0.230311 0.030746 -0.230311
5 product_id unit_price NaN NaN 0.142626 0.142626
6 unit_price cancelled 0.051929 0.064367 0.000133 0.064367
7 product_id total NaN NaN 0.024799 0.024799
8 quantity country NaN NaN 0.020539 0.020539
9 country total NaN NaN 0.016507 0.016507
10 product_id quantity NaN NaN 0.015635 0.015635
11 country cancelled NaN NaN 0.007489 0.007489
12 unit_price country NaN NaN 0.002613 0.002613
13 product_id country NaN NaN 0.001545 0.001545
14 product_id cancelled NaN NaN -0.000172 -0.000172

可用参数#

df.ww.dependence 提供了各种参数用于调整依赖性计算。

  • measure - 要计算哪些依赖性度量。可以提供一个度量列表来一次性计算多个度量。有效的度量字符串:

    • “pearson”: 计算皮尔逊相关系数

    • “spearman”: 计算斯皮尔曼相关系数

    • “mutual_info”: 计算列之间的互信息

    • “max”: 同时计算皮尔逊相关系数和互信息,并为每对列返回 max(abs(pearson), mutual)

    • “all”: 包含 “pearson”、“mutual” 和 “max” 的列

  • num_bins - 为了计算连续数据的互信息,Woodwork 将数值数据分箱到不同的类别中。此参数允许你选择用于对数据进行分类的箱数。

    • 默认使用 10 个箱

    • 箱越多,列的多样性就越大。使用的箱数应准确反映数据的分布情况。

  • nrows - 如果 nrows 设置的值小于 DataFrame 中的行数,则会从底层数据中随机抽样该数量的行。

    • 默认使用所有可用行。

    • 减少行数可以加快对具有大量行的 DataFrame 计算互信息,但应注意采样数量足够大,以便准确反映数据。

  • include_index - 如果设置为 True 且定义了对互信息有效的逻辑类型索引,则索引列将包含在互信息输出中。

    • 默认为 False

现在你了解了参数,可以探索改变箱数。注意——这仅影响数值列 quantityunit_price。将箱数从 10 增加到 50,仅显示受影响的列。

[5]:
dep_df = df.ww.dependence(measures="all", nrows=1000)
dep_df[
    dep_df["column_1"].isin(["unit_price", "quantity"])
    | dep_df["column_2"].isin(["unit_price", "quantity"])
]
/home/docs/checkouts/readthedocs.org/user_builds/feature-labs-inc-datatables/envs/stable/lib/python3.9/site-packages/woodwork/statistics_utils/_get_dependence_dict.py:137: UserWarning: Dropping columns ['order_id', 'customer_name'] to allow mutual information to run faster
  warnings.warn(
[5]:
column_1 column_2 pearson spearman mutual_info max
0 quantity total 0.532443 0.708133 0.217693 0.708133
1 quantity unit_price -0.128447 -0.367468 0.086608 -0.367468
2 unit_price total 0.130113 0.313225 0.104822 0.313225
3 quantity cancelled -0.141777 -0.231845 0.025767 -0.231845
5 product_id unit_price NaN NaN 0.142626 0.142626
6 unit_price cancelled 0.051929 0.064367 0.000133 0.064367
8 quantity country NaN NaN 0.020539 0.020539
10 product_id quantity NaN NaN 0.015635 0.015635
12 unit_price country NaN NaN 0.002613 0.002613
[6]:
dep_df = df.ww.dependence(measures="all", nrows=1000, num_bins=50)
dep_df[
    dep_df["column_1"].isin(["unit_price", "quantity"])
    | dep_df["column_2"].isin(["unit_price", "quantity"])
]
/home/docs/checkouts/readthedocs.org/user_builds/feature-labs-inc-datatables/envs/stable/lib/python3.9/site-packages/woodwork/statistics_utils/_get_dependence_dict.py:137: UserWarning: Dropping columns ['order_id', 'customer_name'] to allow mutual information to run faster
  warnings.warn(
[6]:
column_1 column_2 pearson spearman mutual_info max
0 quantity total 0.532443 0.708133 0.338410 0.708133
1 unit_price total 0.130113 0.313225 0.369177 0.369177
2 quantity unit_price -0.128447 -0.367468 0.105124 -0.367468
3 quantity cancelled -0.141777 -0.231845 0.017920 -0.231845
5 product_id unit_price NaN NaN 0.163247 0.163247
6 unit_price cancelled 0.051929 0.064367 0.000578 0.064367
8 quantity country NaN NaN 0.024711 0.024711
9 product_id quantity NaN NaN 0.019753 0.019753
12 unit_price country NaN NaN 0.006367 0.006367

为了在互信息输出中包含索引列,运行计算时设置 include_index=True

[7]:
dep_df = df.ww.dependence(measures="all", nrows=1000, num_bins=50, include_index=True)
dep_df[
    dep_df["column_1"].isin(["order_product_id"])
    | dep_df["column_2"].isin(["order_product_id"])
]
/home/docs/checkouts/readthedocs.org/user_builds/feature-labs-inc-datatables/envs/stable/lib/python3.9/site-packages/woodwork/statistics_utils/_get_dependence_dict.py:137: UserWarning: Dropping columns ['order_id', 'customer_name'] to allow mutual information to run faster
  warnings.warn(
[7]:
column_1 column_2 pearson spearman mutual_info max
15 order_product_id product_id NaN NaN 2.253749e-10 2.253749e-10
16 order_product_id total NaN NaN 2.000378e-12 2.000378e-12
17 order_product_id quantity NaN NaN 1.125030e-12 1.125030e-12
18 order_product_id unit_price NaN NaN 1.095753e-12 1.095753e-12
19 order_product_id country NaN NaN 2.012074e-13 2.012074e-13
20 order_product_id cancelled NaN NaN 1.495338e-14 1.495338e-14

使用 Series.ww.box_plot_dict 进行离群值检测#

Woodwork 进行单变量离群值检测的一种方法是 IQR(四分位距)方法。这可以使用 series.ww.box_plot_dict 方法按列进行,该方法识别离群值并包含构建箱线图所需的统计数据。

[8]:
total = df.ww["total"]
box_plot_dict = total.ww.box_plot_dict()

print("high bound: ", box_plot_dict["high_bound"])
print("low_bound: ", box_plot_dict["low_bound"])
print("quantiles: ", box_plot_dict["quantiles"])
print("number of low outliers: ", len(box_plot_dict["low_values"]))
print("number of high outliers: ", len(box_plot_dict["high_values"]))
high bound:  71.15625
low_bound:  -31.47375
quantiles:  {0.0: -277974.84, 0.25: 7.0124999999999975, 0.5: 19.305, 0.75: 32.669999999999995, 1.0: 277974.84}
number of low outliers:  1922
number of high outliers:  31016

我们可以看到零售数据集中的 total 列有许多离群值,并且它们更偏向于数据集的顶部。dataframe 中大约有 40 万行,因此约 8% 的值是离群值。我们再看看一个相同长度的服从正态分布的数据列,看看为其生成的统计数据是什么样子。

[9]:
rnd = np.random.RandomState(33)
s = pd.Series(rnd.normal(50, 10, 401604))
s.ww.init()
box_plot_dict = s.ww.box_plot_dict()

print("high bound: ", box_plot_dict["method"])
print("high bound: ", box_plot_dict["high_bound"])
print("low_bound: ", box_plot_dict["low_bound"])
print("quantiles: ", box_plot_dict["quantiles"])
print("number of low outliers: ", len(box_plot_dict["low_values"]))
print("number of high outliers: ", len(box_plot_dict["high_values"]))
high bound:  box_plot
high bound:  77.04098
low_bound:  22.89795
quantiles:  {0.0: 4.519658918840335, 0.25: 43.20158903786463, 0.5: 49.988236390934304, 0.75: 56.73734594188448, 1.0: 95.28094989391388}
number of low outliers:  1460
number of high outliers:  1381

对于正态分布的数据集,出现离群值的可能性更接近我们预期的 .7% 左右。

使用 Series.ww.medcouple_dict 进行离群值检测#

Woodwork 进行单变量离群值检测的另一种方法是通过 medcouple 统计量,通过 series.ww.medcouple_dict 实现。与 series.ww.box_plot_dict 类似,此方法也返回构建箱线图所需的信息。

[10]:
total = df.ww["total"]
medcouple_dict = total.ww.medcouple_dict()

print("medcouple: ", medcouple_dict["method"])
print("medcouple: ", medcouple_dict["medcouple_stat"])
print("high bound: ", medcouple_dict["high_bound"])
print("low_bound: ", medcouple_dict["low_bound"])
print("quantiles: ", medcouple_dict["quantiles"])
print("number of low outliers: ", len(medcouple_dict["low_values"]))
print("number of high outliers: ", len(medcouple_dict["high_values"]))
medcouple:  medcouple
medcouple:  0.111
high bound:  71.40484
low_bound:  -31.22676
quantiles:  {0.0: -277974.84, 0.25: 7.0124999999999975, 0.5: 19.305, 0.75: 32.669999999999995, 1.0: 277974.84}
number of low outliers:  1928
number of high outliers:  31001

识别出的离群值数量已减少到约 7% 的行。此外,这些离群值的方向也发生了变化,识别出的下部离群值更多,上部离群值更少。这是因为 medcouple 统计量在确定哪些点最可能是离群值时考虑了分布的偏度。对于长尾分布,箱线图方法可能不太适用。medcouple 统计量最多基于列中 10,000 个随机抽样的观测值进行计算。此值可以在 Config 设置中通过 ww.config.set_option("medcouple_sample_size", N) 更改。

[11]:
rnd = np.random.RandomState(33)
s = pd.Series(rnd.normal(50, 10, 401604))
s.ww.init()
medcouple_dict = s.ww.medcouple_dict()
print("medcouple: ", medcouple_dict["medcouple_stat"])
print("high bound: ", medcouple_dict["high_bound"])
print("low_bound: ", medcouple_dict["low_bound"])
print("quantiles: ", medcouple_dict["quantiles"])
print("number of low outliers: ", len(medcouple_dict["low_values"]))
print("number of high outliers: ", len(medcouple_dict["high_values"]))
medcouple:  -0.003
high bound:  77.04126
low_bound:  22.89823
quantiles:  {0.0: 4.519658918840335, 0.25: 43.20158903786463, 0.5: 49.988236390934304, 0.75: 56.73734594188448, 1.0: 95.28094989391388}
number of low outliers:  1460
number of high outliers:  1380

如你所见,medcouple 统计量接近 0,这表明数据几乎没有偏度。当数据正常时,通过 Medcouple 统计量识别出的离群值数量与通过箱线图方法识别出的数量大致相同。

使用 Series.ww.get_outliers 进行离群值检测#

知道是使用箱线图方法还是 medcouple 方法可能会令人困惑。你可能不知道分布是否偏斜,如果是,也不知道是否偏斜得足够大以支持使用 medcouple_dict。方法 get_outliers 可以用来解决这些问题。使用默认方法 best 运行 series.ww.get_outliers 将根据最佳方法返回离群值信息。

这是通过对序列进行随机抽样计算 medcouple 统计量来确定的。如果该抽样的 medcouple 统计量的绝对值小于默认值 0.3,则选择的方法将是箱线图。如果大于或等于 0.3,则使用 medcouple 方法(因为这表示分布中至少存在中等程度的偏度)。

要更改此默认的 medcouple 阈值 0.3,请随时通过 Config 设置中的 ww.config.set_option("medcouple_threshold", 0.5) 更改该值。

从含噪时间序列数据推断频率#

df.ww.infer_temporal_frequencies 将推断每个时间列的观测频率(每日、每两周、每年等),即使是在含噪数据上。如果一个时间列主要是一种频率,但以任何方式存在噪声(即包含重复时间戳、nan、间隙或与整体频率不符的时间戳),此表访问器方法将提供最可能的频率以及有关不符合此频率的数据行信息。

推断无噪声数据#

如果你的时间序列数据是完美的且不包含任何噪声数据,df.ww.infer_temporal_frequencies() 将返回一个字典,其中键是每个时间列的列名,值是 pandas 别名字符串。

[12]:
df = pd.DataFrame(
    {
        "idx": range(100),
        "dt1": pd.date_range("2005-01-01", periods=100, freq="H"),
        "dt2": pd.date_range("2005-01-01", periods=100, freq="B"),
    }
)
df.ww.init()
df.ww.infer_temporal_frequencies()
/tmp/ipykernel_1249/2560597039.py:4: FutureWarning: 'H' is deprecated and will be removed in a future version, please use 'h' instead.
  "dt1": pd.date_range("2005-01-01", periods=100, freq="H"),
[12]:
{'dt1': 'h', 'dt2': 'B'}

推断含噪声数据(缺失值)#

如果你的时间序列数据存在噪声,并且你传递了 debug=True 标志,返回的字典还将包含每个时间列的 debug 对象。此对象有助于你了解数据中存在问题的位置。

[13]:
dt1_a = pd.date_range(end="2005-01-01 10:00:00", periods=500, freq="H")
dt1_b = pd.date_range(start="2005-01-01 15:00:00", periods=500, freq="H")


df = pd.DataFrame(
    {
        "idx": range(1000),
        "dt1": dt1_a.append(dt1_b),
    }
)
df.ww.init()

infer_dict = df.ww.infer_temporal_frequencies(debug=True)

inferred_freq, debug_object = infer_dict["dt1"]

assert inferred_freq is None

debug_object
/tmp/ipykernel_1249/4098424264.py:1: FutureWarning: 'H' is deprecated and will be removed in a future version, please use 'h' instead.
  dt1_a = pd.date_range(end="2005-01-01 10:00:00", periods=500, freq="H")
/tmp/ipykernel_1249/4098424264.py:2: FutureWarning: 'H' is deprecated and will be removed in a future version, please use 'h' instead.
  dt1_b = pd.date_range(start="2005-01-01 15:00:00", periods=500, freq="H")
[13]:
{'actual_range_start': '2004-12-11T15:00:00',
 'actual_range_end': '2005-01-22T10:00:00',
 'message': None,
 'estimated_freq': 'h',
 'estimated_range_start': '2004-12-11T15:00:00',
 'estimated_range_end': '2005-01-22T10:00:00',
 'duplicate_values': [],
 'missing_values': [{'dt': '2005-01-01T11:00:00', 'idx': 500, 'range': 4}],
 'extra_values': [],
 'nan_values': []}

在上面的例子中,我们可以看到元组的第一个元素是 None,因为时间序列有错误,无法推断。

debug_object 中,我们可以清楚地看到 estimated_freq 是表示每小时的 H,还有一些额外信息,我们将在下面解释。

Debug 对象描述#

Debug 对象包含以下信息:

  • actual_range_start: 观测到的开始时间的 ISO 8601 格式字符串

  • actual_range_end: 观测到的结束时间的 ISO 8601 格式字符串

  • message: 描述为何无法推断频率的消息

  • estimated_freq: 推断出的频率

  • estimated_range_start: 推断的开始时间的 ISO 8601 格式字符串

  • estimated_range_end: 推断的结束时间的 ISO 8601 格式字符串

  • duplicate_values: 重复值范围对象数组(如下所述)

  • missing_values: 缺失值范围对象数组(如下所述)

  • extra_values: 额外值范围对象数组(如下所述)

  • nan_values: nan 值范围对象数组(如下所述)

范围对象包含以下信息:

  • dt: 此范围中第一个时间戳的 ISO 8601 格式字符串

  • idx: 此范围中第一个时间戳的索引

    • 对于重复值和额外值,idx 是指观测数据

    • 对于缺失值,idx 是指推断数据。

  • range: 此范围的长度。

通过以下示例可以最好地理解此信息

Time Series Timeline

在上图所示中,你可以看到左侧是预期的时间序列,右侧是观测到的时间序列。此时间序列有以下错误:

  • 重复值

    • “01:00:00” 在观测索引 1 和 2 处重复两次

  • 缺失值

    • “04:00:00” 在推断索引 3 处缺失

  • 额外值

    • “06:20:00” 是在观测索引 7 处的额外值

我们可以在下面的代码中重新创建这个示例。请注意,由于我们在时间序列开头填充了良好数据,索引偏移了 500。

[14]:
dt_a = pd.date_range(end="2005-01-01T00:00:00.000Z", periods=500, freq="H").to_series()
dt_b = [
    "2005-01-01T01:00:00.000Z",
    "2005-01-01T01:00:00.000Z",
    "2005-01-01T01:00:00.000Z",
    "2005-01-01T02:00:00.000Z",
    "2005-01-01T03:00:00.000Z",
    "2005-01-01T05:00:00.000Z",
    "2005-01-01T06:00:00.000Z",
    "2005-01-01T06:20:00.000Z",
    "2005-01-01T07:00:00.000Z",
    "2005-01-01T08:00:00.000Z",
]
dt_b = pd.Series([pd.Timestamp(d) for d in dt_b])
dt_c = pd.date_range(
    start="2005-01-01T09:00:00.000Z", periods=500, freq="H"
).to_series()

dt = pd.concat([dt_a, dt_b, dt_c]).reset_index(drop=True)

dt = pd.to_datetime(dt, utc=True)

df = pd.DataFrame({"dt": dt})
df.ww.init()
df.ww.infer_temporal_frequencies(debug=True)
/tmp/ipykernel_1249/3056248247.py:1: FutureWarning: 'H' is deprecated and will be removed in a future version, please use 'h' instead.
  dt_a = pd.date_range(end="2005-01-01T00:00:00.000Z", periods=500, freq="H").to_series()
/tmp/ipykernel_1249/3056248247.py:15: FutureWarning: 'H' is deprecated and will be removed in a future version, please use 'h' instead.
  dt_c = pd.date_range(
[14]:
{'dt': (None,
  {'actual_range_start': '2004-12-11T05:00:00',
   'actual_range_end': '2005-01-22T04:00:00',
   'message': None,
   'estimated_freq': 'h',
   'estimated_range_start': '2004-12-11T05:00:00',
   'estimated_range_end': '2005-01-22T04:00:00',
   'duplicate_values': [{'dt': '2005-01-01T01:00:00', 'idx': 501, 'range': 2}],
   'missing_values': [{'dt': '2005-01-01T04:00:00', 'idx': 503, 'range': 1}],
   'extra_values': [{'dt': '2005-01-01T06:20:00', 'idx': 507, 'range': 1}],
   'nan_values': []})}