表操作#

在本节中,我们将描述高级操作,这些操作可用于从一个或多个输入表生成新表。这包括:


文档

描述

功能

Grouped operations

按键对表和列分组

group_by()

Binning

装箱台

group_by()

Stack vertically

沿行连接输入表

vstack()

Stack horizontally

沿列连接输入表

hstack()

Join

两个表的数据库样式联接

join()

Unique rows

按键列出的唯一表行

unique()

Set difference

设置两个表的差

setdiff()

Table diff

两个简单表的一般差异

report_diff_values()

分组操作#

有时在表或表列中,数据集中有一些自然组,为这些组计算一些派生值是有意义的。一个最小的例子是一个从各种观测运行中获取的具有光度测定的对象的列表:

>>> 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:44:7 ,以及 7:10obs_by_name 桌子。

输出分组表具有两个重要属性:

  • 按按词法排序的键值的顺序排列的组 (M101M31M82 在我们的示例中)。

  • 每个组中的行的顺序与它们在原始表中的显示顺序相同。

最初的论点是 (keys ),用于 group_by() 函数可以采用多种输入数据类型:

  • 具有表列名的单个字符串值(如上所示)

  • 具有表列名的字符串值列表

  • 另一 TableColumn 和桌子一样长

  • numpy 与表长度相同的结构化数组

  • numpy 与表长度相同的齐次数组

在所有情况下,对应的行元素都被视为 tuple 这些值构成用于对原始表进行排序并生成所需组的键值。

举个例子,为了得到每个观测夜每个物体的平均震级,我们首先将这两个表分组 nameobs_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() 方法。这是通过提供为每个组调用的函数来实现的。传递给此方法的函数必须接受两个参数:

  • tableTable 对象

  • key_colnames :中的列列表 table 用作分组键

然后它也必须返回 TrueFalse .

例子#

下面将选择非键列中只有正值的所有表组:

>>> 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 == -2a == 0 在非键列中有所有的正值,因此这些是被选中的值。

同样,分组的列可以使用 filter() 方法,但在本例中,筛选函数只接受一个参数,即列组。它还是必须返回 TrueFalse 。例如::

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 metadataMerging 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_typemetadata_conflicts 就像 vstack() 功能。这个 join_type 参数控制如何处理输入表的列中的不匹配。

另请参阅 Merging metadataMerging 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() 方法允许您根据“键列”中的匹配值将这两个表合并到一个表中。默认情况下,键列是两个表共有的一组列。在本例中,键列是 nameobs_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_1obs_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射线数据感到惊讶。请记住,缺省匹配键包括两者 nameobs_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_leftkeys_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_leftkeys_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 metadataMerging column attributes 有关如何将输入表的这些特征合并到单个输出表中的详细信息。

合并详细信息#

当组合两个或多个表时,需要合并输入中的某些特征,并可能解决冲突。本节描述了该过程。

列重命名#

在输入表有冲突列名的情况下,有一种机制可以生成唯一的输出列名。有两个关键字参数控制重命名行为:

table_names

为要联接的表提供名称的字符串的列表。默认情况下,这是 ['1', '2', ...] ,其中数字对应于输入表。

uniq_col_name

默认值为的字符串格式说明符 '{{col_name}}_{{table_name}}' .

通过使用 opticalxray 表中 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 :按列元数据作为有序字典

这里描述的表操作处理将输入表中的元数据合并到单个输出结构中的任务。因为元数据可以是任意复杂的,所以没有唯一的方法来进行合并。当前的实现使用递归算法,有四条规则:

  • dict 元素按键合并。

  • 相互冲突 listtuple 元素串联在一起。

  • 相互冲突 dict 通过递归调用Merge函数来合并元素。

  • 冲突的元素不是 listtuple ,或 dict 将遵循以下规则:

    • 如果两个元数据值相同,则输出设置为该值。

    • 如果其中一个冲突的元数据值是 None ,另一个值被拾取。

    • 如果两个元数据值不同,且两者都不相同 None ,将拾取列表中最后一个表的。

默认情况下,在最后一种情况下会发出警告(两个元数据值都不会 None ). 可以使用 metadata_conflicts 参数 hstack()vstack()join() . 这个 metadata_conflicts 选项可以设置为:

  • 'silent' –不发出任何警告,最后一个表的值将被自动选取。

  • 'warn' –发出警告,拾取最后一个表的值。

  • 'error' –引发异常。

属性的子类可以扩充或自定义合并元数据的默认策略 MergeStrategy 基类。在大多数情况下,您还将使用 enable_merge_strategies() 用于启用自定义策略。链接的文档字符串提供了详细信息。

合并列属性#

除了表和列之外 meta 属性,列属性 unitformat ,以及 description 通过按顺序遍历输入表并获取定义的最后一个值(即,未定义的 None )。

例子#

合并列属性 unitformatdescription ::

>>> 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 metadatametadata_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.KDTreescipy.spatial.KDTree.query_ball_tree() ,这些对象的行为可以通过 kdtree_argsquery_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