表操作#
在本节中,我们将描述高级操作,这些操作可用于从一个或多个输入表生成新表。这包括:
文档 |
描述 |
功能 |
---|---|---|
按键对表和列分组 |
||
装箱台 |
||
沿行连接输入表 |
||
沿列连接输入表 |
||
两个表的数据库样式联接 |
||
按键列出的唯一表行 |
||
设置两个表的差 |
||
两个简单表的一般差异 |
分组操作#
有时在表或表列中,数据集中有一些自然组,为这些组计算一些派生值是有意义的。一个最小的例子是一个从各种观测运行中获取的具有光度测定的对象的列表:
>>> from astropy.table import Table
>>> obs = Table.read("""name obs_date mag_b mag_v
... M31 2012-01-02 17.0 17.5
... M31 2012-01-02 17.1 17.4
... M101 2012-01-02 15.1 13.5
... M82 2012-02-14 16.2 14.5
... M31 2012-02-14 16.9 17.3
... M82 2012-02-14 15.2 15.5
... M101 2012-02-14 15.0 13.6
... M82 2012-03-26 15.7 16.5
... M101 2012-03-26 15.1 13.5
... M101 2012-03-26 14.8 14.3
... """, format='ascii')
>>> # Make sure magnitudes are printed with one digit after the decimal point
>>> obs['mag_b'].info.format = '{:.1f}'
>>> obs['mag_v'].info.format = '{:.1f}'
表组#
现在假设我们需要每个物体的平均震级。我们首先根据 name
带有 group_by()
方法。这将返回一个按排序的新表 name
它有一个 groups
属性指定的唯一值 name
以及相应的表行:
>>> obs_by_name = obs.group_by('name')
>>> print(obs_by_name)
name obs_date mag_b mag_v
---- ---------- ----- -----
M101 2012-01-02 15.1 13.5 << First group (index=0, key='M101')
M101 2012-02-14 15.0 13.6
M101 2012-03-26 15.1 13.5
M101 2012-03-26 14.8 14.3
M31 2012-01-02 17.0 17.5 << Second group (index=4, key='M31')
M31 2012-01-02 17.1 17.4
M31 2012-02-14 16.9 17.3
M82 2012-02-14 16.2 14.5 << Third group (index=7, key='M83')
M82 2012-02-14 15.2 15.5
M82 2012-03-26 15.7 16.5
<< End of groups (index=10)
>>> print(obs_by_name.groups.keys)
name
----
M101
M31
M82
>>> print(obs_by_name.groups.indices)
[ 0 4 7 10]
这个 groups
属性是所有具有表和列的分组操作的门户。它定义了如何通过唯一行关键字值的数组和这些关键字值的组边界索引对表进行分组。此处的组对应于行切片 0:4
, 4:7
,以及 7:10
在 obs_by_name
桌子。
输出分组表具有两个重要属性:
按按词法排序的键值的顺序排列的组 (
M101
,M31
,M82
在我们的示例中)。每个组中的行的顺序与它们在原始表中的显示顺序相同。
最初的论点是 (keys
),用于 group_by()
函数可以采用多种输入数据类型:
在所有情况下,对应的行元素都被视为 tuple
这些值构成用于对原始表进行排序并生成所需组的键值。
举个例子,为了得到每个观测夜每个物体的平均震级,我们首先将这两个表分组 name
和 obs_date
如下:
>>> print(obs.group_by(['name', 'obs_date']).groups.keys)
name obs_date
---- ----------
M101 2012-01-02
M101 2012-02-14
M101 2012-03-26
M31 2012-01-02
M31 2012-02-14
M82 2012-02-14
M82 2012-03-26
操纵组#
将分组应用于表之后,就可以访问组的单个组或子集。在所有情况下,这将返回一个新的分组表。例如,要获取与第二个组(index=1)对应的子表,请执行以下操作:
>>> print(obs_by_name.groups[1])
name obs_date mag_b mag_v
---- ---------- ----- -----
M31 2012-01-02 17.0 17.5
M31 2012-01-02 17.1 17.4
M31 2012-02-14 16.9 17.3
要将第一组和第二组组合在一起,请使用 slice
**
>>> groups01 = obs_by_name.groups[0:2]
>>> print(groups01)
name obs_date mag_b mag_v
---- ---------- ----- -----
M101 2012-01-02 15.1 13.5
M101 2012-02-14 15.0 13.6
M101 2012-03-26 15.1 13.5
M101 2012-03-26 14.8 14.3
M31 2012-01-02 17.0 17.5
M31 2012-01-02 17.1 17.4
M31 2012-02-14 16.9 17.3
>>> print(groups01.groups.keys)
name
----
M101
M31
您也可以提供 numpy
用于选择特定组的索引数组或布尔掩码,例如:
>>> mask = obs_by_name.groups.keys['name'] == 'M101'
>>> print(obs_by_name.groups[mask])
name obs_date mag_b mag_v
---- ---------- ----- -----
M101 2012-01-02 15.1 13.5
M101 2012-02-14 15.0 13.6
M101 2012-03-26 15.1 13.5
M101 2012-03-26 14.8 14.3
可以使用以下命令迭代组子表和相应的键:
>>> for key, group in zip(obs_by_name.groups.keys, obs_by_name.groups):
... print(f'****** {key["name"]} *******')
... print(group)
... print('')
...
****** M101 *******
name obs_date mag_b mag_v
---- ---------- ----- -----
M101 2012-01-02 15.1 13.5
M101 2012-02-14 15.0 13.6
M101 2012-03-26 15.1 13.5
M101 2012-03-26 14.8 14.3
****** M31 *******
name obs_date mag_b mag_v
---- ---------- ----- -----
M31 2012-01-02 17.0 17.5
M31 2012-01-02 17.1 17.4
M31 2012-02-14 16.9 17.3
****** M82 *******
name obs_date mag_b mag_v
---- ---------- ----- -----
M82 2012-02-14 16.2 14.5
M82 2012-02-14 15.2 15.5
M82 2012-03-26 15.7 16.5
列组#
喜欢 Table
物体, Column
还可以对对象进行分组,以便使用分组操作进行后续操作。这可以同时应用于 Table
或者光秃秃的 Column
物体。
至于 Table
,则使用 group_by()
方法。这里的区别在于没有提供一个或多个列名的选项,因为这对于 Column
。
实例#
要在列中生成分组:
>>> from astropy.table import Column
>>> import numpy as np
>>> c = Column([1, 2, 3, 4, 5, 6], name='a')
>>> key_vals = np.array(['foo', 'bar', 'foo', 'foo', 'qux', 'qux'])
>>> cg = c.group_by(key_vals)
>>> for key, group in zip(cg.groups.keys, cg.groups):
... print(f'****** {key} *******')
... print(group)
... print('')
...
****** bar *******
a
---
2
****** foo *******
a
---
1
3
4
****** qux *******
a
---
5
6
聚集#
聚合是将指定的归约函数应用于每个非键列的每个组中的值的过程。此函数必须接受 numpy.ndarray
作为第一个参数,并返回单个标量值。常见的函数示例有 numpy.sum()
, numpy.mean()
,以及 numpy.std()
。
对于示例分组表 obs_by_name
从上面开始,我们用 aggregate()
方法:
>>> obs_mean = obs_by_name.groups.aggregate(np.mean)
AstropyUserWarning: Cannot aggregate column 'obs_date' with type '<U10': ...
>>> print(obs_mean)
name mag_b mag_v
---- ----- -----
M101 15.0 13.7
M31 17.0 17.4
M82 15.7 15.5
看起来震级值被成功地平均了,但是 AstropyUserWarning
?自.以来 obs_date
列是字符串类型的数组,则 numpy.mean()
函数失败并引发异常 cannot perform reduceat with flexible type
。任何时候发生这种情况 aggregate()
将发出警告,然后从输出结果中删除该列。请注意, name
列是 keys
用于确定分组,以便自动从聚合中忽略它。
从分组表中,可以选择要对其执行聚合的一个或多个列:
>>> print(obs_by_name['mag_b'].groups.aggregate(np.mean))
mag_b
-----
15.0
17.0
15.7
还可以指定列的顺序::
>>> print(obs_by_name['name', 'mag_v', 'mag_b'].groups.aggregate(np.mean))
name mag_v mag_b
---- ----- -----
M101 13.7 15.0
M31 17.4 17.0
M82 15.5 15.7
也可以聚合单个数据列:
>>> c = Column([1, 2, 3, 4, 5, 6], name='a')
>>> key_vals = np.array(['foo', 'bar', 'foo', 'foo', 'qux', 'qux'])
>>> cg = c.group_by(key_vals)
>>> cg_sums = cg.groups.aggregate(np.sum)
>>> for key, cg_sum in zip(cg.groups.keys, cg_sums):
... print(f'Sum for {key} = {cg_sum}')
...
Sum for bar = 2
Sum for foo = 8
Sum for qux = 11
如果指定的函数具有 numpy.ufunc.reduceat()
方法,则将改为调用此方法。对于具有许多相对较小的组的大型未屏蔽表或列,这可以将性能提高10到100倍(或更多)。它还允许使用某些 numpy
通常接受多个输入数组但也用作约简函数的函数,如 numpy.add
。这个 numpy
应充分利用 numpy.ufunc.reduceat()
包括:
在特殊情况下, numpy.sum()
和 numpy.mean()
替换为其各自的 reduceat
方法:研究方法。
过滤#
表组可以通过 filter()
方法。这是通过提供为每个组调用的函数来实现的。传递给此方法的函数必须接受两个参数:
table
:Table
对象key_colnames
:中的列列表table
用作分组键
例子#
下面将选择非键列中只有正值的所有表组:
>>> def all_positive(table, key_colnames):
... colnames = [name for name in table.colnames if name not in key_colnames]
... for colname in colnames:
... if np.any(table[colname] <= 0):
... return False
... return True
使用此函数的一个示例是:
>>> t = Table.read(""" a b c
... -2 7.0 2
... -2 5.0 1
... 1 3.0 -5
... 1 -2.0 -6
... 1 1.0 7
... 0 4.0 4
... 3 3.0 5
... 3 -2.0 6
... 3 1.0 7""", format='ascii')
>>> tg = t.group_by('a')
>>> t_positive = tg.groups.filter(all_positive)
>>> for group in t_positive.groups:
... print(group)
... print('')
...
a b c
--- --- ---
-2 7.0 2
-2 5.0 1
a b c
--- --- ---
0 4.0 4
从中可以看出,只有 a == -2
和 a == 0
在非键列中有所有的正值,因此这些是被选中的值。
同样,分组的列可以使用 filter()
方法,但在本例中,筛选函数只接受一个参数,即列组。它还是必须返回 True
或 False
。例如::
def all_positive(column):
return np.all(column > 0)
装箱#
分析中常用的工具是基于某个参考值对表进行分位。示例:
双星在一段时间内的几个波段的光度测定,这些波段应按轨道相位组合。
通过一次合并100行来降低表的采样密度。
历史数据抽样不均,每年应分为四个点。
所有这些装箱表的例子都可以使用 grouped operations . 该部分中的示例集中于离散键值的情况,例如源的名称。在本节中,我们将展示一种简洁而强大的方法,即应用分组操作来完成对时间、阶段或行号等键值的binning。
所有这些情况下的共同主题是将键值数组转换成一个新的浮点或int值数组,其值对于同一个输出bin中的行是相同的。
例子#
例如,我们生成一个假光曲线:
>>> year = np.linspace(2000.0, 2010.0, 200) # 200 observations over 10 years
>>> period = 1.811
>>> y0 = 2005.2
>>> mag = 14.0 + 1.2 * np.sin(2 * np.pi * (year - y0) / period)
>>> phase = ((year - y0) / period) % 1.0
>>> dat = Table([year, phase, mag], names=['year', 'phase', 'mag'])
现在,我们制作一个数组,用于以0.25年的间隔将数据分块:
>>> year_bin = np.trunc(year / 0.25)
它具有这样的属性,即每个0.25年柱中的所有样本都具有相同的值 year_bin
。想一想 year_bin
作为的仓号 year
。然后通过分组并立即使用以下项聚合来进行装箱 numpy.mean()
。
>>> dat_grouped = dat.group_by(year_bin)
>>> dat_binned = dat_grouped.groups.aggregate(np.mean)
我们可以用 plt.plot(dat_binned['year'], dat_binned['mag'], '.')
. 或者,我们可以分为10个阶段:
>>> phase_bin = np.trunc(phase / 0.1)
>>> dat_grouped = dat.group_by(phase_bin)
>>> dat_binned = dat_grouped.groups.aggregate(np.mean)
这次,试着用 plt.plot(dat_binned['phase'], dat_binned['mag'])
.
垂直堆叠#
这个 Table
类支持垂直堆叠表格。 vstack()
功能。此过程通常也称为沿行方向连接或附加表。它大致对应于 numpy.vstack()
功能。
实例#
假设我们有两个观测表,它们有几个共同的列名:
>>> from astropy.table import Table, vstack
>>> obs1 = Table.read("""name obs_date mag_b logLx
... M31 2012-01-02 17.0 42.5
... M82 2012-10-29 16.2 43.5
... M101 2012-10-31 15.1 44.5""", format='ascii')
>>> obs2 = Table.read("""name obs_date logLx
... NGC3516 2011-11-11 42.1
... M31 1999-01-05 43.1
... M82 2012-10-30 45.0""", format='ascii')
现在我们可以把这两张表叠起来:
>>> print(vstack([obs1, obs2]))
name obs_date mag_b logLx
------- ---------- ----- -----
M31 2012-01-02 17.0 42.5
M82 2012-10-29 16.2 43.5
M101 2012-10-31 15.1 44.5
NGC3516 2011-11-11 -- 42.1
M31 1999-01-05 -- 43.1
M82 2012-10-30 -- 45.0
请注意, obs2
表中缺少 mag_b
列,因此在堆叠输出表中,这些值被标记为缺少。这是默认行为,对应于 join_type='outer'
。属性还有另外两个允许值。 join_type
论点, 'inner'
和 'exact'
::
>>> print(vstack([obs1, obs2], join_type='inner'))
name obs_date logLx
------- ---------- -----
M31 2012-01-02 42.5
M82 2012-10-29 43.5
M101 2012-10-31 44.5
NGC3516 2011-11-11 42.1
M31 1999-01-05 43.1
M82 2012-10-30 45.0
>>> print(vstack([obs1, obs2], join_type='exact'))
Traceback (most recent call last):
...
TableMergeError: Inconsistent columns in input arrays (use 'inner'
or 'outer' join_type to allow non-matching columns)
在.的情况下 join_type='inner'
,输出表中只有公共列(交叉点)。什么时候 join_type='exact'
是指定的,则 vstack()
要求所有输入表具有完全相同的列名。
通过提供更长的表格列表,可以堆叠两个以上的表格:
>>> obs3 = Table.read("""name obs_date mag_b logLx
... M45 2012-02-03 15.0 40.5""", format='ascii')
>>> print(vstack([obs1, obs2, obs3]))
name obs_date mag_b logLx
------- ---------- ----- -----
M31 2012-01-02 17.0 42.5
M82 2012-10-29 16.2 43.5
M101 2012-10-31 15.1 44.5
NGC3516 2011-11-11 -- 42.1
M31 1999-01-05 -- 43.1
M82 2012-10-30 -- 45.0
M45 2012-02-03 15.0 40.5
另请参阅 Merging metadata 和 Merging column attributes 有关如何将输入表的这些特征合并到单个输出表中的详细信息。另请注意,您可以使用单个表 Row
而不是将完整的表格作为输入之一。
水平堆叠#
这个 Table
类支持水平(按列方向)堆叠表。 hstack()
功能。它大致对应于 numpy.hstack()
功能。
实例#
假设我们有以下两个表:
>>> from astropy.table import Table, hstack
>>> t1 = Table.read("""a b c
... 1 foo 1.4
... 2 bar 2.1
... 3 baz 2.8""", format='ascii')
>>> t2 = Table.read("""d e
... ham eggs
... spam toast""", format='ascii')
现在我们可以水平地堆叠这两个表:
>>> print(hstack([t1, t2]))
a b c d e
--- --- --- ---- -----
1 foo 1.4 ham eggs
2 bar 2.1 spam toast
3 baz 2.8 -- --
和以前一样 vstack()
,有一个可选的 join_type
可以取值的参数 'inner'
, 'exact'
,以及 'outer'
。缺省值为 'outer'
,它有效地获得了可用行的并集,并掩盖了任何缺失的值。上面的例子说明了这一点。其他选项提供行的交集,其中 'exact'
要求所有表的行数完全相同::
>>> print(hstack([t1, t2], join_type='inner'))
a b c d e
--- --- --- ---- -----
1 foo 1.4 ham eggs
2 bar 2.1 spam toast
>>> print(hstack([t1, t2], join_type='exact'))
Traceback (most recent call last):
...
TableMergeError: Inconsistent number of rows in input arrays (use 'inner' or
'outer' join_type to allow non-matching rows)
通过提供更长的表格列表,可以堆叠两个以上的表格。下面的示例还说明了输入列名冲突时的行为(请参阅 Column renaming 有关详情):
>>> t3 = Table.read("""a b
... M45 2012-02-03""", format='ascii')
>>> print(hstack([t1, t2, t3]))
a_1 b_1 c d e a_3 b_3
--- --- --- ---- ----- --- ----------
1 foo 1.4 ham eggs M45 2012-02-03
2 bar 2.1 spam toast -- --
3 baz 2.8 -- -- -- --
输入表中的元数据由中描述的过程合并 Merging metadata 一节。另请注意,您可以使用单个表 Row
而不是将完整的表格作为输入之一。
堆栈深度方面#
这个 Table
类支持在表内深度堆叠列。 dstack()
功能。它大致相当于运行 numpy.dstack()
函数作用于按名称匹配的各个列。
实例#
假设我们有源的数据表,给出了不同PSF分数的封闭源计数的信息:
>>> from astropy.table import Table, dstack
>>> src1 = Table.read("""psf_frac counts
... 0.10 45.
... 0.50 90.
... 0.90 120.
... """, format='ascii')
>>> src2 = Table.read("""psf_frac counts
... 0.10 200.
... 0.50 300.
... 0.90 350.
... """, format='ascii')
现在,我们可以将这两个表按深度叠加,得到一个具有两个源特性的表:
>>> srcs = dstack([src1, src2])
>>> print(srcs)
psf_frac counts
---------- --------------
0.1 .. 0.1 45.0 .. 200.0
0.5 .. 0.5 90.0 .. 300.0
0.9 .. 0.9 120.0 .. 350.0
在这种情况下,第一个源的计数可以作为 srcs['counts'][:, 0]
,同样地,第二个源计数是 srcs['counts'][:, 1]
.
对于此函数,所有输入表的长度必须相同。此函数可以接受 join_type
和 metadata_conflicts
就像 vstack()
功能。这个 join_type
参数控制如何处理输入表的列中的不匹配。
另请参阅 Merging metadata 和 Merging column attributes 有关如何将输入表的这些特征合并到单个输出表中的详细信息。另请注意,您可以使用单个表 Row
而不是将完整的表格作为输入之一。
加入#
这个 Table
类支持 database join 操作。这为基于一个或多个键列中的值组合表提供了一种灵活而强大的方法。
实例#
假设我们有两个观测表,第一个是B和V量级,第二个是重叠(但不完全相同)样品的X射线光度:
>>> from astropy.table import Table, join
>>> optical = Table.read("""name obs_date mag_b mag_v
... M31 2012-01-02 17.0 16.0
... M82 2012-10-29 16.2 15.2
... M101 2012-10-31 15.1 15.5""", format='ascii')
>>> xray = Table.read(""" name obs_date logLx
... NGC3516 2011-11-11 42.1
... M31 1999-01-05 43.1
... M82 2012-10-29 45.0""", format='ascii')
这个 join()
方法允许您根据“键列”中的匹配值将这两个表合并到一个表中。默认情况下,键列是两个表共有的一组列。在本例中,键列是 name
和 obs_date
. 我们可以在同一日期找到同一物体的所有观测结果,如下所示:
>>> opt_xray = join(optical, xray)
>>> print(opt_xray)
name obs_date mag_b mag_v logLx
---- ---------- ----- ----- -----
M82 2012-10-29 16.2 15.2 45.0
我们可以通过 name
只有通过提供 keys
参数,它可以是单个列名或列名列表::
>>> print(join(optical, xray, keys='name'))
name obs_date_1 mag_b mag_v obs_date_2 logLx
---- ---------- ----- ----- ---------- -----
M31 2012-01-02 17.0 16.0 1999-01-05 43.1
M82 2012-10-29 16.2 15.2 2012-10-29 45.0
这个输出表包含了一个物体(M31和M82)的光学和X射线数据的所有观测值。注意,自从 obs_date
列出现在两个表中,它已拆分为两个列, obs_date_1
和 obs_date_2
. 数值取自“左” (optical
)和“对” (xray
)分别是表。
不同的连接选项#
到目前为止,表联接被称为“内部”联接,表示键列上两个表的严格交集。
如果你想做一个新的表 每一个 左表中的行,并包括右表中的匹配值(如果可用),这称为左联接:
>>> print(join(optical, xray, join_type='left'))
name obs_date mag_b mag_v logLx
---- ---------- ----- ----- -----
M101 2012-10-31 15.1 15.5 --
M31 2012-01-02 17.0 16.0 --
M82 2012-10-29 16.2 15.2 45.0
其中两个观测没有X射线数据,如 --
在桌子上。您可能会对输出中没有M31的X射线数据感到惊讶。请记住,缺省匹配键包括两者 name
和 obs_date
。将该键指定为仅 name
专栏给出::
>>> print(join(optical, xray, join_type='left', keys='name'))
name obs_date_1 mag_b mag_v obs_date_2 logLx
---- ---------- ----- ----- ---------- -----
M101 2012-10-31 15.1 15.5 -- --
M31 2012-01-02 17.0 16.0 1999-01-05 43.1
M82 2012-10-29 16.2 15.2 2012-10-29 45.0
同样,您可以使用 join_type='right'
.
要使用两个表中的行的并集创建表,请执行“外部”联接:
>>> print(join(optical, xray, join_type='outer'))
name obs_date mag_b mag_v logLx
------- ---------- ----- ----- -----
M101 2012-10-31 15.1 15.5 --
M31 1999-01-05 -- -- 43.1
M31 2012-01-02 17.0 16.0 --
M82 2012-10-29 16.2 15.2 45.0
NGC3516 2011-11-11 -- -- 42.1
在上述所有情况下,输出联接表将按键列排序,并且通常不会保留输入表的行顺序。
最后,您可以执行“笛卡尔”连接,它是所有可用行的笛卡尔乘积。在本例中,没有键列(并提供 keys
参数是错误的)::
>>> print(join(optical, xray, join_type='cartesian'))
name_1 obs_date_1 mag_b mag_v name_2 obs_date_2 logLx
------ ---------- ----- ----- ------- ---------- -----
M31 2012-01-02 17.0 16.0 NGC3516 2011-11-11 42.1
M31 2012-01-02 17.0 16.0 M31 1999-01-05 43.1
M31 2012-01-02 17.0 16.0 M82 2012-10-29 45.0
M82 2012-10-29 16.2 15.2 NGC3516 2011-11-11 42.1
M82 2012-10-29 16.2 15.2 M31 1999-01-05 43.1
M82 2012-10-29 16.2 15.2 M82 2012-10-29 45.0
M101 2012-10-31 15.1 15.5 NGC3516 2011-11-11 42.1
M101 2012-10-31 15.1 15.5 M31 1999-01-05 43.1
M101 2012-10-31 15.1 15.5 M82 2012-10-29 45.0
键列名不相同#
要使用 join()
函数使用不同的键列名称,请使用 keys_left
和 keys_right
争论。在下面的示例中,一个表有一个 'name'
列,而另一个列具有 'obj_id'
专栏::
>>> optical = Table.read("""name obs_date mag_b mag_v
... M31 2012-01-02 17.0 16.0
... M82 2012-10-29 16.2 15.2
... M101 2012-10-31 15.1 15.5""", format='ascii')
>>> xray_1 = Table.read("""obj_id obs_date logLx
... NGC3516 2011-11-11 42.1
... M31 1999-01-05 43.1
... M82 2012-10-29 45.0""", format='ascii')
要根据对象名称执行匹配,请执行以下操作:
>>> print(join(optical, xray_1, keys_left='name', keys_right='obj_id'))
name obs_date_1 mag_b mag_v obj_id obs_date_2 logLx
---- ---------- ----- ----- ------ ---------- -----
M31 2012-01-02 17.0 16.0 M31 1999-01-05 43.1
M82 2012-10-29 16.2 15.2 M82 2012-10-29 45.0
这个 keys_left
和 keys_right
参数还可以采用列名列表,甚至可以采用类似列的对象的列表。后一种情况允许独立于要联接的表指定匹配的键列值。
相同的键值#
这个 Table
即使有多行具有相同的键值,联接操作也有效。例如,下表中的列有多行 'key'
**
>>> from astropy.table import Table, join
>>> left = Table([[0, 1, 1, 2], ['L1', 'L2', 'L3', 'L4']], names=('key', 'L'))
>>> right = Table([[1, 1, 2, 4], ['R1', 'R2', 'R3', 'R4']], names=('key', 'R'))
>>> print(left)
key L
--- ---
0 L1
1 L2
1 L3
2 L4
>>> print(right)
key R
--- ---
1 R1
1 R2
2 R3
4 R4
在这些表上执行外连接表明,实际发生的是 Cartesian product 。对于每个匹配键,表示左和右表的每一种组合。如果左侧或右侧的表中没有匹配项,则相应的列值将被指定为MISSING::
>>> print(join(left, right, join_type='outer'))
key L R
--- --- ---
0 L1 --
1 L2 R1
1 L2 R2
1 L3 R1
1 L3 R2
2 L4 R3
4 -- R4
内部连接相同,但只返回左表和右表中都有键匹配的行::
>>> print(join(left, right, join_type='inner'))
key L R
--- --- ---
1 L2 R1
1 L2 R2
1 L3 R1
1 L3 R2
2 L4 R3
输入表名称中的冲突由上一节中描述的过程处理 Column renaming . 另请参见 Merging metadata 和 Merging column attributes 有关如何将输入表的这些特征合并到单个输出表中的详细信息。
合并详细信息#
当组合两个或多个表时,需要合并输入中的某些特征,并可能解决冲突。本节描述了该过程。
列重命名#
在输入表有冲突列名的情况下,有一种机制可以生成唯一的输出列名。有两个关键字参数控制重命名行为:
table_names
为要联接的表提供名称的字符串的列表。默认情况下,这是
['1', '2', ...]
,其中数字对应于输入表。uniq_col_name
默认值为的字符串格式说明符
'{{col_name}}_{{table_name}}'
.
通过使用 optical
和 xray
表中 join()
先前定义的示例:
>>> print(join(optical, xray, keys='name',
... table_names=['OPTICAL', 'XRAY'],
... uniq_col_name='{table_name}_{col_name}'))
name OPTICAL_obs_date mag_b mag_v XRAY_obs_date logLx
---- ---------------- ----- ----- ------------- -----
M31 2012-01-02 17.0 16.0 1999-01-05 43.1
M82 2012-10-29 16.2 15.2 2012-10-29 45.0
合并元数据#
Table
对象可以具有关联的元数据:
Table.meta
:作为有序字典的表级元数据Column.meta
:按列元数据作为有序字典
这里描述的表操作处理将输入表中的元数据合并到单个输出结构中的任务。因为元数据可以是任意复杂的,所以没有唯一的方法来进行合并。当前的实现使用递归算法,有四条规则:
默认情况下,在最后一种情况下会发出警告(两个元数据值都不会 None
). 可以使用 metadata_conflicts
参数 hstack()
, vstack()
或 join()
. 这个 metadata_conflicts
选项可以设置为:
'silent'
–不发出任何警告,最后一个表的值将被自动选取。'warn'
–发出警告,拾取最后一个表的值。'error'
–引发异常。
属性的子类可以扩充或自定义合并元数据的默认策略 MergeStrategy
基类。在大多数情况下,您还将使用 enable_merge_strategies()
用于启用自定义策略。链接的文档字符串提供了详细信息。
合并列属性#
除了表和列之外 meta
属性,列属性 unit
, format
,以及 description
通过按顺序遍历输入表并获取定义的最后一个值(即,未定义的 None
)。
例子#
合并列属性 unit
, format
或 description
::
>>> from astropy.table import Column, Table, vstack
>>> col1 = Column([1], name='a')
>>> col2 = Column([2], name='a', unit='cm')
>>> col3 = Column([3], name='a', unit='m')
>>> t1 = Table([col1])
>>> t2 = Table([col2])
>>> t3 = Table([col3])
>>> out = vstack([t1, t2, t3])
MergeConflictWarning: In merged column 'a' the 'unit' attribute does
not match (cm != m). Using m for merged output
>>> out['a'].unit
Unit("m")
合并的规则与 Merging metadata 和 metadata_conflicts
选项还控制列属性的合并。
连接坐标和自定义连接函数#
具有以下内容的源目录 SkyCoord
可以使用具有指定距离阈值的坐标的交叉匹配来联接坐标列。这是键列值的“模糊”匹配的更一般问题的特例,在这种情况下,我们只需要近似匹配,而不是精确匹配。这是通过使用 join_funcs
争论。
警告
当至少有一个表中的相关条目彼此之间的距离超过连接距离的两倍时,本节中讨论的坐标和距离表连接最适用。如果这一点不能令人满意,则连接结果可能是意外的。
这是该算法的结果,该算法有效地找到邻近点的簇(等价类),并为两个表中的每个条目分配唯一的簇标识符。这假设连接匹配函数是传递关系,其中 join_func(A, B)
和 join_func(B, C)
暗示 join_func(A, C)
。在左侧和右侧都有多个匹配的情况下,具有单个簇标识符的点簇有可能在大小上扩展到超过距离阈值。
属性之外提供其他联接键时,用户应特别注意此问题 join_funcs
。该代码不对其他键执行“预联接”,因此在两个表的距离内有重叠的可能性更高。
例子#
连接上的两个表 SkyCoord
键列,我们使用 join_funcs
关键字以提供 dict
指定如何按名称匹配特定键列的函数的。在下面的示例中,我们加入了 sc
列,因此我们提供以下参数:
join_funcs={'sc': join_skycoord(0.2 * u.deg)}
这说明了 join()
若要匹配 sc
使用JOIN函数的键列 join_skycoord()
匹配距离阈值为0.2度。在引擎盖下,这叫 search_around_sky()
或 search_around_3d()
进行交叉比对。默认情况下,使用 search_around_sky()
(角度)匹配,但是 search_around_3d()
(长度或无量纲)也可用。这是使用 distance_func
的论点 join_skycoord()
,它也可以是与的输入和输出API匹配的函数 search_around_sky()
。
现在我们展示整个过程:
>>> from astropy.coordinates import SkyCoord
>>> import astropy.units as u
>>> from astropy.table import Table, join, join_skycoord
>>> sc1 = SkyCoord([0, 1, 1.1, 2], [0, 0, 0, 0], unit='deg')
>>> sc2 = SkyCoord([1.05, 0.5, 2.1], [0, 0, 0], unit='deg')
>>> t1 = Table([sc1, [0, 1, 2, 3]], names=['sc', 'idx'])
>>> t2 = Table([sc2, [0, 1, 2]], names=['sc', 'idx'])
>>> t12 = join(t1, t2, keys='sc', join_funcs={'sc': join_skycoord(0.2 * u.deg)})
>>> print(t12)
sc_id sc_1 idx_1 sc_2 idx_2
deg,deg deg,deg
----- ------- ----- -------- -----
1 1.0,0.0 1 1.05,0.0 0
1 1.1,0.0 2 1.05,0.0 0
2 2.0,0.0 3 2.1,0.0 2
联接表与0.2度范围内的源匹配,并创建了一个新列 sc_id
每个源都有一个唯一的标识符。
您可能想知道上面定义的联接函数中发生了什么,特别是如果您对定义自己的此类函数感兴趣的话。可以这样做,以便允许表的模糊字匹配,例如,在名称并不总是完全匹配的情况下,按名称连接人员表。
这里首先要注意的是, join_skycoord()
函数实际上返回函数本身。这允许通过功能框指定可变匹配距离。联接函数的要求是它接受与两个键列对应的两个参数,并返回 (ids1, ids2)
。这些标识符对应于具有唯一匹配源的每个列条目的标识。
>>> join_func = join_skycoord(0.2 * u.deg)
>>> join_func(sc1, sc2) # Associate each coordinate with unique source ID
(array([3, 1, 1, 2]), array([1, 4, 2]))
如果您想编写自己的模糊匹配函数,我们建议从源代码开始 join_skycoord()
或 join_distance()
。
距离连接#
上面的示例侧重于加入一个 SkyCoord
属性连接列值之间的一般距离。 join_distance()
联接函数。这可以应用于一维或二维(向量)列。这看起来与坐标示例非常相似,但这里有更多的灵活性。匹配是使用 scipy.spatial.KDTree
和 scipy.spatial.KDTree.query_ball_tree()
,这些对象的行为可以通过 kdtree_args
和 query_args
分别进行了论证。
唯一行#
有时,只使用表中具有唯一键列的行甚至是完全唯一的行是有意义的。这可以使用上面描述的 group_by()
方法和方法 groups
属性,或使用 unique()
方便功能。这个 unique()
函数返回一个已排序的表,其中包含每个唯一的 keys
列值。如果没有 keys
则返回一个包含所有完全唯一行的排序表。
例子#
一个可能希望使用具有唯一键列的行的情况的示例是一个从不同观察运行中进行光度测量的对象列表。使用 'name'
作为唯一的 keys
,它返回三个目标中每个目标的第一个匹配项:
>>> from astropy import table
>>> obs = table.Table.read("""name obs_date mag_b mag_v
... M31 2012-01-02 17.0 17.5
... M82 2012-02-14 16.2 14.5
... M101 2012-01-02 15.1 13.5
... M31 2012-01-02 17.1 17.4
... M101 2012-01-02 15.1 13.5
... M82 2012-02-14 16.2 14.5
... M31 2012-02-14 16.9 17.3
... M82 2012-02-14 15.2 15.5
... M101 2012-02-14 15.0 13.6
... M82 2012-03-26 15.7 16.5
... M101 2012-03-26 15.1 13.5
... M101 2012-03-26 14.8 14.3
... """, format='ascii')
>>> unique_by_name = table.unique(obs, keys='name')
>>> print(unique_by_name)
name obs_date mag_b mag_v
---- ---------- ----- -----
M101 2012-01-02 15.1 13.5
M31 2012-01-02 17.0 17.5
M82 2012-02-14 16.2 14.5
将多个列用作 keys
::
>>> unique_by_name_date = table.unique(obs, keys=['name', 'obs_date'])
>>> print(unique_by_name_date)
name obs_date mag_b mag_v
---- ---------- ----- -----
M101 2012-01-02 15.1 13.5
M101 2012-02-14 15.0 13.6
M101 2012-03-26 15.1 13.5
M31 2012-01-02 17.0 17.5
M31 2012-02-14 16.9 17.3
M82 2012-02-14 16.2 14.5
M82 2012-03-26 15.7 16.5
设置差分#
集合差异将告诉您第一个集合中包含的元素,而另一个集合中不包含的元素。此概念可以应用于表的行,方法是使用 setdiff()
功能。您为函数提供了两个输入表,它将返回第一个表中没有出现在第二个表中的所有行。
可选的 keys
参数指定用于匹配表行的列的名称。这可以是列的完整列表的子集,但第一个和第二个表都必须包含由指定的所有列 keys
. 如果未提供,则 keys
默认为第一个表中的所有列名。
如果没有找到不同的行,则 setdiff()
函数将返回一个空表。
例子#
下面的示例说明使用两个表中列的公共子集来查找两个观察列表的集合差:
>>> from astropy.table import Table, setdiff
>>> cat_1 = Table.read("""name obs_date mag_b mag_v
... M31 2012-01-02 17.0 16.0
... M82 2012-10-29 16.2 15.2
... M101 2012-10-31 15.1 15.5""", format='ascii')
>>> cat_2 = Table.read(""" name obs_date logLx
... NGC3516 2011-11-11 42.1
... M31 2012-01-02 43.1
... M82 2012-10-29 45.0""", format='ascii')
>>> sdiff = setdiff(cat_1, cat_2, keys=['name', 'obs_date'])
>>> print(sdiff)
name obs_date mag_b mag_v
---- ---------- ----- -----
M101 2012-10-31 15.1 15.5
在本例中,第一个表中有一个列在第二个表中不存在,因此 keys
参数必须用于指定所需的列名。
表格差异#
你可以用两张表来比较 report_diff_values()
,生成的报告与 FITS diff .
例子#
下面的示例说明如何查找两个表之间的差异:
>>> from astropy.table import Table
>>> from astropy.utils.diff import report_diff_values
>>> import sys
>>> cat_1 = Table.read("""name obs_date mag_b mag_v
... M31 2012-01-02 17.0 16.0
... M82 2012-10-29 16.2 15.2
... M101 2012-10-31 15.1 15.5""", format='ascii')
>>> cat_2 = Table.read("""name obs_date mag_b mag_v
... M31 2012-01-02 17.0 16.5
... M82 2012-10-29 16.2 15.2
... M101 2012-10-30 15.1 15.5
... NEW 2018-05-08 nan 9.0""", format='ascii')
>>> identical = report_diff_values(cat_1, cat_2, fileobj=sys.stdout)
name obs_date mag_b mag_v
---- ---------- ----- -----
a> M31 2012-01-02 17.0 16.0
? ^
b> M31 2012-01-02 17.0 16.5
? ^
M82 2012-10-29 16.2 15.2
a> M101 2012-10-31 15.1 15.5
? ^
b> M101 2012-10-30 15.1 15.5
? ^
b> NEW 2018-05-08 nan 9.0
>>> identical
False