一个用于N维分类数据的库。
项目描述
catii :cat::eyes
一个用于N维分类数据的库。
倒排索引(iindex)
N维数据值可以用多种方式表示。一个非常常见的格式是“矩形”或“表格”或“宽”格式,一个N维数组,其中每个观测值是一个“行”,每个单元格值是一个“类别ID”或其他值。这可以通过添加“列”和更高维度的轴扩展到两个或更多维度。
相同的信息可以存储为“倒排索引”,就像我们在这里做的那样,通过存储坐标元组,包括“类别ID”作为第一个坐标;“列”或更高维度是元组中的附加坐标。
倒排索引可以通过省略索引中最常见的类别而比等效数组小得多。任何行ID不在索引中的行都假设具有常见值。在许多领域,数据集越大,其稀疏度越高。
例如,矩形数组:
a = [1, 0, 4, 0, 1, 1, 4, 1]
...变成一个形状为(8,)的iindex,一个“常见”值为1,两个条目:
>>> from catii import iindex
>>> iindex.from_array(a)
iindex(shape=(8,), common=1, entries={(0,): array([1, 3], dtype=uint32), (4,): array([2, 6], dtype=uint32)})
矩形数组:
b = [
[2, 2, 2],
[2, 0, 2],
[2, 2, 4],
[2, 0, 2],
[2, 2, 2],
[2, 2, 4],
]
...变成一个形状为(6, 3)的iindex,一个“常见”值为2,二维条目:
>>> iindex.from_array(b)
iindex(shape=(6, 3), common=2, entries={(0, 1): array([1, 3], dtype=uint32), (4, 2): array([2, 5], dtype=uint32)})
>>> _.to_array()
array([[2, 2, 2],
[2, 0, 2],
[2, 2, 4],
[2, 0, 2],
[2, 2, 2],
[2, 2, 4]], dtype=uint8)
通常认为iindex.common是最常见的值,但有可能构造出一个iindex实例,其中这个值并不正确。调用shift_common()来规范化这个值。因此,两个索引可能代表相同的数据值,但具有不同的常见值,如果其中一个或两个尚未规范化,则这些值不会使用==
相等。
索引与数组
尽管iindex类可以表示与NumPy数组相同的信息,但它故意没有实现NumPy API,因为将数组和索引混合通常会导致将稀疏索引粗略地转换成密集数组,从而抹去索引的所有优势。遍历索引中的所有值不应该很容易。相反,索引实现了用于1)统计分析,以及2)更改和与其他索引交互的方法。
索引在这些任务中的性能通常与数组相当或更好——对于一维大约75%的密度,对于二维或更多维度大约40%。如果你有大型密集数组,或者包含数千个不同值的数组,你应该考虑混合索引/数组方法,或者直接使用数组或其他方法。
每个条目的rowid数组假定是排序的,这允许进行多个优化。然而,出于速度的考虑,索引类本身并不自动验证这一点。你可以调用index.validate()来检查这一点以及其他许多不变性,但请注意,这通常很慢,在生产代码中一旦验证过就应避免。
第一个坐标始终是“互斥”类别;额外的坐标不是互斥的。例如,行ID 0不应该同时出现在(1,7)和(2,7),但它可以同时出现在(1,7)和(1,8)。想象一个变量“你喜欢这个三明治吗?”其中“1”表示“是”和“2”表示“否”是第一个坐标,而“7”表示“芝士三明治”,“8”表示“BLT”是第二个坐标。你不太可能需要表示选择“是”和“否”的芝士三明治的人,但很常见的是表示选择两个三明治都“是”的人。
与大多数数组实现不同,不同值的机器类型不是重点——它们可以是任何可散列的Python类型。在实践中,特别是在与数组混合时,通常最好将坐标视为正是这样:在离散值的一维线性空间中的点,通常是连续整数,从0开始。这在使用ccubes(以下)时是必需的,因此以整数形式存储数据可以节省宝贵的查询时间。任何名称或其他属性应该是独立的元数据。
条件立方体(ccube)
分析通常包括变量的汇总统计(iindexes或数组),通常基于或“按”其他iindexes分组。例如,你可能想知道有多少人与特定的政党(比如,0=None,1=Rep,2=Dem)有关联,但按教育水平分组,以查看这两个变量之间是否存在任何显著的关联。使用catii,你可以使用ccube计算这个值。
>>> from catii import ccube, iindex
>>> party = iindex({(1,): [0, 2, 5], (2,): [4]}, common=0, shape=(8,))
>>> educ = iindex({(0,): [2, 5, 4], (2,): [4]}, common=1, shape=(8,))
>>> ccube([educ, party]).count()
array([[1, 2, 0],
[3, 1, 0],
[0, 0, 1]])
返回的数组是一个二维的“超立方体”:一个列联表(或“交叉表”),表示这两个变量在样本中的频率分布。你可能熟悉以更表格的形式看到它
party
0 1 2
e -- -- --
d 0 | 1 2 0
u 1 | 3 1 0
c 2 | 0 0 1
我们将两个变量作为ccube的维度传递,并要求它们的交互计数。由于我们提供了两个一维iindexes,输出是二维的。如果我们的输入iindexes有额外的轴,输出将是三维或更多。
维度必须行对应;也就是说,当我们说“ educ变量在行5的值为0”和“ party变量在行5的值为1”时,我们指的是“行5”在两者中指的是我们数据中的相同观察结果。我们按行聚合,因此它们永远不会是我们输出中的维度。
当维度索引包括额外的轴时,它们被认为是独立的,并且ccube会遍历它们的笛卡尔积。对于每个更高轴的组合,它形成每个维度的1-D切片的子立方体,并将它们的输出堆叠。例如,如果我们用“流派”代替二维的“流派”变量来记录人们喜欢或不喜欢哪些音乐流派(例如,0=古典,1=流行,2=另类),我们会在我们的立方体输出中看到额外的维度。喜欢/不喜欢轴将具有不同的值,因此是iindex元组中的第一个坐标。流派轴将放置在第二个坐标。
>>> genre = iindex.from_array([
[0, 0, 0],
[0, 0, 1],
[0, 1, 0],
[2, 1, 1],
[1, 0, 0],
[2, 2, 1]
])
>>> genre
iindex(shape=(6, 3), common=0, entries={
(1, 0): array([4], dtype=uint32),
(1, 1): array([2, 3], dtype=uint32),
(1, 2): array([1, 3, 5], dtype=uint32),
(2, 0): array([3, 5], dtype=uint32),
(2, 1): array([5], dtype=uint32)
})
>>> ccube([genre]).count()
array([[3, 1, 2], # classical
[3, 2, 1], # pop
[3, 3, nan]]) # alternative
额外的轴始终被移动到最外层(相同的顺序),因此上面的结果首先遍历流派轴,然后更紧密地遍历缺失/喜欢/不喜欢值。
频率函数(ffuncs)
除了计数之外,catii还提供了其他聚合函数与立方体一起使用:总和、平均值和有效计数。这些函数都至少接受一个额外的“事实变量”作为参数;也就是说,要加和、平均或计数有效值的数值。这些也必须按行对应于立方体维度。
这些操作就像计数一样,并返回一个结果立方体。
权重
所有这些ffunc都接受一个可选的“权重”参数。如果提供,它也必须按行对应于维度和任何事实变量。然后函数通过这些值对这些数据进行加权。例如
>>> from catii import ccube, iindex
>>> import numpy
>>> party = iindex({(1,): [0, 2, 5], (2,): [4]}, common=0, shape=(8,))
>>> ccube([party]).count()
array([4, 3, 1])
>>> weights = numpy.arange(10) / 10.0
>>> weights
array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])
>>> ccube([party]).count(weights)
array([3.4, 0.7, 0.4])
缺失值
事实变量和权重变量可以采用两种形式:*一个单独的NumPy数组,其中缺失值由NaN或NaT表示*一个NumPy数组的对,其中第一个包含值,第二个是布尔值的“有效性”数组:True表示“有效”,False表示“缺失”。在False的情况下,第一个数组中对应的值被忽略。
在一定程度上,你选择的格式取决于你的应用程序以及你的数据如何表示。但是,请注意,NumPy数组没有标准的方式来表示缺失值。而不是为这些类型指定哨兵值,你可以传递一个单独的布尔值“有效性”数组,并且你可能因此考虑对所有dtype都这样做。请注意这会稍微快一些。
这里提供的所有函数都具有一个reduce
方法,该方法返回NumPy数组形式的立方体输出;这些输出中也可能有缺失值在任何单元格中
- 没有输入行具有与它对应的立方体维度,或者
- 有行,但对应的事实值或权重是缺失值(如果是
ignore_missing
则“all”,否则“any”)。
这与标准的NumPy略有不同,例如,numpy.sum([])默认为0.0。然而,catii的消费者通常需要区分[0, 0]的和与[]的和。当然,你可以自由地接受输出并将arr[arr.isnan()]设置为0,如果你愿意的话。
每个ffunc都有一个可选的ignore_missing
参数。如果为False(默认值),则任何缺失值(值为NaN或有效性为False)都会传播,以便输出在事实变量或权重中任何行中具有缺失值的任何单元格中也有缺失值。如果ignore_missing
为True,则此类输入行将被忽略,并且不贡献于输出,就像NumPy的nanmean
或R的na.rm = TRUE
一样。请注意这也更快且使用更少的内存。
这里的reduce
方法默认为“单个NumPy数组”格式,用NaN值表示缺失。传递例如return_missing_as=(0, False)
以返回一个包含(值,有效性)数组的2元组。在这种情况下,函数将用0替换values
数组中的NaN值。如果你希望例如sum([])
返回0,而没有第二个“有效性”数组,则传递return_missing_as=0
。
组合立方体计算
通常,当我们找到像加权计数这样的摘要时,我们也希望得到非加权计数,或者我们想要显示均值,但是使用“基本”计数。我们可以简单地形成一个立方体,并调用每个快捷方法。
>>> c = ccube([educ, party])
>>> c.mean(arr)
>>> c.count()
然而,这需要两次形成维度的交互。如果我们的教育和党派变量有数百万行,或者非常密集,或者有数百个类别,或者额外的轴,或者如果我们交叉额外的变量,这个步骤可以迅速增加执行时间。您可以通过使用ccube.calculate并传递一个ffuncs列表来节省时间。
>>> from catii import ffuncs
>>> c = ccube([educ, party])
>>> means, counts = c.calculate([ffuncs.ffunc_mean(arr), ffuncs.ffunc_count()])
这将对我们的教育和党派维度进行一次迭代,并将每个坐标的每个唯一组合传递给每个ffunc。
矩形列联立方体(xcube)和频率函数(xfuncs)
如果您的维度变量是NumPy数组,将它们转换为iindexes可能很昂贵。通常,这应该在写入时完成,以便读取,特别是ccubes,可以更快。有时,您没有选择,或者某个特定变量非常密集(大约75%以上),作为NumPy数组实际上更小。此外,并非每个聚合函数都能使用ccubes使用的“边缘差异”方法实现:例如,标准差使用平方根,这不可逆。
catii包提供了一个“xcube”对象,当您不能使用iindexes作为立方体维度时可以回退到它。xcube与ccube工作方式相同,但将NumPy数组作为维度而不是iindexes。有一组“xfuncs”,它们对应并扩展了您在ccubes中使用的“ffuncs”集合。一般来说,即使您的维度高达约75%密集,ccube/ffunc的性能也优于相同的xcube/xfunc。但在许多领域,数据越大,越稀疏。90-99%稀疏的维度可以在一个或两个数量级更短的时间内立方。
项目详情
catii-1.0.0a14.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | ef5e448c625baf4cc716cda745d800a8ef9891cd34a20a92e57905a08e83ed61 |
|
MD5 | da41d16b3e0051eb07bbafefd816b5f8 |
|
BLAKE2b-256 | c19ba2a4acf1154d260e0158d36b334e55ea546442d5260376336f9e4f4bb90c |