【问题标题】:xarray expand_dim to add higher level dimensionxarray expand_dim 添加更高级别的维度
【发布时间】:2021-01-23 20:52:56
【问题描述】:

我正在尝试连接一个数据数组列表,然后添加一个维度,以便为每个连接的数据数组加上标签。我认为这是 expand_dims 的一个用例,但是在尝试了 SO 的各种解决方案之后,我被卡住了。我想我缺少关于 xarray 的一些基本知识。 这些似乎最接近:

  1. Add a 'time' dimension to xarray Dataset and assign coordinates from another Dataset to it

  2. Add 'constant' dimension to xarray Dataset

我使用 pandas 数据框从文件名中编译元数据,然后对组进行分组和迭代以创建数据集,使用 skimage.io.ImageCollection 将多个图像文件加载到 nparray 中,并最终创建 xarray 对象

独立示例

设置

#%%  load libraries
from itertools import product
from PIL import Image
import numpy as np
import pandas as pd
import xarray as xr
import glob
from skimage import io
import re

#%% Synthetic data generator
ext = 'png'
delim = '_'

datadir = os.path.join('data','syn')
os.makedirs(datadir, exist_ok=True)
cartag = ['A1', 'A2']
date = ['2020-05-31', '2020-06-01', '2020-06-02']
frame = ['Fp', 'Fmp']
parameter = ['FvFm','t40', 't60']
list_vals = [cartag, date, frame, parameter]
mesh = list(product(*list_vals))
mesh = np.array(mesh)
for entry in mesh:
    print(entry)
    img = np.random.random_sample((8, 8))*255
    img = img.astype('uint8')
    fn = delim.join(entry)+'.png'
    pimg = Image.fromarray(img)
    pimg.save(os.path.join(datadir,fn))

#%% import synthetic images
fns = [
    fn for fn in glob.glob(pathname=os.path.join(datadir, '*%s' % ext))
]
flist = list()
for fullfn in fns:
    fn = os.path.basename(fullfn)
    fn,_ = os.path.splitext(fn)
    f = fn.split(delim)
    f.append(fullfn)
    flist.append(f)

fdf = pd.DataFrame(flist,
                columns=[
                    'plantbarcode', 'timestamp',
                    'frame','parameter', 'filename'
                ])
fdf=fdf.sort_values(['timestamp','plantbarcode','parameter','frame'])

函数定义

#%%
def get_tind_seconds(parameter):
    tind = re.search("\d+", parameter)
    if tind is not None:
        tind = int(tind.group())
    elif parameter == 'FvFm':
        tind = 0
    else:
        raise ValueError("the parameter '%s' is not supported" % parameter)
    return (tind)

xarray 部分

dfgrps = fdf.groupby(['plantbarcode', 'timestamp', 'parameter'])
ds = list()
for grp, grpdf in dfgrps:
    # print(grpdf.parameter.unique())
    parameter = grpdf.parameter.unique()[0]
    tind = get_tind_seconds(
        parameter
    )  #tind is an integer representing seconds since start of experiment
    # print(tind)

    filenames = grpdf.filename.to_list()
    imgcol = io.ImageCollection(filenames)
    imgstack = imgcol.concatenate()  #imgstack is now 2x8x8 ndarray
    indf = grpdf.frame  #the 2 dim are frames Fp and Fmp
    # print(indf)
    arr = xr.DataArray(name=parameter,
                       data=imgstack,
                       dims=('frame', 'y', 'x'),
                       coords={
                    #        'frame': indf,
                           'parameter': [parameter,parameter]
                    #        'tind_s': [tind,tind]
                       },
                       attrs={
                           'jobdate': grpdf.timestamp.unique()[0],
                           'plantbarcode': grpdf.plantbarcode.unique()[0]
                       })
    # arr = arr.expand_dims(
    #     dims={'tind_s': tind}
    # )  #<- somehow I need to label each dataarray with another dimension assigning it the dim/coord `tind`
    ds.append(arr)

dstest = xr.concat(ds, dim='parameter')

我们的目标是每天都有一个不同的文件,plantbarcode。所以在这种情况下是 4 个文件。 其中图像可以通过参数和帧进行索引。 tind_s 通常对于为每个参数绘制每个图像的摘要统计信息很有用,所以我也想制作昏暗/坐标 - 我不确定何时使用哪个。看起来 dim 必须匹配传入的数据,因此在本例中为 2 帧 x 8x8 像素。

原创

我使用 pandas 数据框从文件名编译元数据(这里是前几个条目)

    frameid plantbarcode    experiment  datetime    jobdate cameralabel filename    frame   parameter
4   5   A1  doi 2020-05-31 21:01:55 2020-06-01  PSII0   data/psII/A1-doi-20200531T210155-PSII0-5.png    Fp  FvFm
5   6   A1  doi 2020-05-31 21:01:55 2020-06-01  PSII0   data/psII/A1-doi-20200531T210155-PSII0-6.png    Fmp FvFm
6   7   A1  doi 2020-05-31 21:01:55 2020-06-01  PSII0   data/psII/A1-doi-20200531T210155-PSII0-7.png    Fp  t40_ALon
7   8   A1  doi 2020-05-31 21:01:55 2020-06-01  PSII0   data/psII/A1-doi-20200531T210155-PSII0-8.png    Fmp t40_ALon
8   9   A1  doi 2020-05-31 21:01:55 2020-06-01  PSII0   data/psII/A1-doi-20200531T210155-PSII0-9.png    Fp  t60_ALon
9   10  A1  doi 2020-05-31 21:01:55 2020-06-01  PSII0   data/psII/A1-doi-20200531T210155-PSII0-10.png   Fmp t60_ALon
...

然后对groups进行分组迭代创建数据集,使用skimage.io.ImageCollection将多个图像文件加载到nparray中,最终创建xarray对象

import os
import cppcpyutils as cppc
import re
from skimage import io
import xarray as xr
import numpy as np
import pandas as pd

delimiter = "(.{2})-(.+)-(\d{8}T\d{6})-(.+)-(\d+)"

filedf = cppc.io.import_snapshots('data/psII', camera='psII', delimiter=delimiter)
filedf = filedf.reset_index().set_index('frameid')

pimframes_map = pd.read_csv('data/pimframes_map.csv',index_col = 'frameid')

filedf = filedf.join(pimframes_map, on = 'frameid').reset_index().query('frameid not in [3,4,5,6]')
dfgrps = filedf.groupby(['experiment', 'plantbarcode', 'jobdate', 'datetime', 'parameter'])

ds=list()
for grp, grpdf in dfgrps:
    # print(grpdf.parameter.unique())
    parameter = grpdf.parameter.unique()[0]
    tind = get_tind_seconds(parameter) #tind is an integer representing seconds since start of experiment
    # print(tind)

    filenames = grpdf.filename.to_list()
    imgcol = io.ImageCollection(filenames)
    imgstack = imgcol.concatenate() #imgstack is now 2x640x480 ndarray
    indf = grpdf.frame #the 2 dim are frames Fp and Fmp
    # print(indf)
    arr = xr.DataArray(name=parameter,
                      data=imgstack,
                      dims=('induction frame','y', 'x'),
                      coords={'induction frame': indf},
                      attrs={'plantbarcode': grpdf.plantbarcode.unique()[0],
                            'jobdate': grpdf.jobdate.unique()[0]})
    arr = arr.expand_dims(dims = {'tind_s': tind}) #<- somehow I need to label each dataarray with another dimension assigning it the dim/coord `tind`
    ds.append(arr)

expand_dims 行导致ValueError: dimensions ('dims',) must have the same length as the number of data dimensions, ndim=0

如果我尝试遵循我在上面链接的第二个 SO,我提供 'tind_s' 作为坐标,它抱怨相对于暗淡有太多。

ValueError: coordinate tind_s has dimensions ('tind_s',), but these are not a subset of the DataArray dimensions ('induction frame', 'y', 'x')

然后我想在 tind_s 是坐标的地方连接在一起

dstest=xr.concat(ds[0:4], dim = 'tind_s')

又一次尝试

我确实发现我可以在imgstack 上使用np.expand_dims(),然后指定额外的dim 和coord,但它会产生一个nan 数组。此外,xr.concat() 的结果是数据数组而不是数据集,因此无法保存(?)。 xarray 中是否有直接的方法可以做到这一点? 我还将属性转换为暗淡

dfgrps = filedf.groupby(
    ['experiment', 'plantbarcode', 'jobdate', 'datetime', 'parameter'])

dalist = list()
for grp, grpdf in dfgrps:
    print(grpdf.parameter.unique())
    parameter = grpdf.parameter.unique()[0]
    tind = get_tind_seconds(parameter)
    # print(tind)
    print(grpdf.plantbarcode.unique())
    print(grpdf.jobdate.unique()[0])

    filenames = grpdf.filename.to_list()
    imgcol = io.ImageCollection(filenames)
    imgstack = imgcol.concatenate()
    imgstack = np.expand_dims(imgstack, axis=0)
    imgstack = np.expand_dims(imgstack, axis=0)
    imgstack = np.expand_dims(imgstack, axis=0)
    indf = grpdf.frame  #xr.Variable('induction frame', grpdf.frame)
    # tind = xr.Variable('tind', [tind])
    # print(indf)
    arr = xr.DataArray(data=imgstack,
                       dims=('jobdate','plantbarcode', 'tind_s', 'induction frame', 'y',
                             'x'),
                       coords={
                           'plantbarcode': grpdf.plantbarcode.unique(),
                           'tind_s': [tind],
                           'induction frame': indf,
                           'jobdate': grpdf.jobdate.unique()}
    )
    dalist.append(arr)

ds = xr.concat(dalist, dim='jobdate')

for 循环之后:print(arr)

<xarray.DataArray (jobdate: 1, plantbarcode: 1, tind_s: 1, induction frame: 2, y: 640, x: 480)>
array([[[[[[0, 0, 0, ..., 0, 0, 0],
           [1, 1, 0, ..., 0, 0, 0],
           [0, 0, 2, ..., 0, 0, 0],
           ...,
           [1, 0, 0, ..., 0, 1, 0],
           [1, 0, 0, ..., 0, 0, 1],
           [1, 0, 0, ..., 1, 1, 0]],

          [[0, 0, 0, ..., 0, 1, 1],
           [2, 2, 0, ..., 0, 0, 1],
           [2, 1, 1, ..., 0, 0, 0],
           ...,
           [0, 1, 0, ..., 1, 0, 1],
           [1, 0, 0, ..., 0, 1, 1],
           [0, 0, 0, ..., 0, 0, 0]]]]]], dtype=uint8)
Coordinates:
  * plantbarcode     (plantbarcode) object 'A2'
  * tind_s           (tind_s) int64 60
  * induction frame  (induction frame) object 'Fp' 'Fmp'
  * jobdate          (jobdate) datetime64[ns] 2020-06-03
Dimensions without coordinates: y, x

print(ds)


print(ds)
<xarray.DataArray (jobdate: 18, plantbarcode: 2, tind_s: 3, induction frame: 2, y: 640, x: 480)>
array([[[[[[ 0.,  0.,  0., ...,  0.,  0.,  1.],
           [ 0.,  0.,  1., ...,  2.,  0.,  0.],
           [ 0.,  0.,  0., ...,  0.,  0.,  0.],
           ...,
           [ 1.,  0.,  0., ...,  7.,  0.,  0.],
           [ 0.,  2.,  4., ...,  0.,  0.,  4.],
           [ 0.,  1.,  0., ...,  1.,  0.,  0.]],

          [[ 0.,  1.,  0., ...,  0.,  1.,  0.],
           [ 0.,  0.,  1., ...,  1.,  2.,  1.],
           [ 0.,  1.,  1., ...,  1.,  0.,  0.],
           ...,
           [ 1.,  2.,  2., ...,  0.,  1.,  1.],
           [ 1.,  1.,  1., ...,  0.,  1.,  0.],
           [ 0.,  0.,  2., ...,  0.,  0.,  1.]]],


         [[[nan, nan, nan, ..., nan, nan, nan],
           [nan, nan, nan, ..., nan, nan, nan],
           [nan, nan, nan, ..., nan, nan, nan],
...
           [nan, nan, nan, ..., nan, nan, nan],
           [nan, nan, nan, ..., nan, nan, nan],
           [nan, nan, nan, ..., nan, nan, nan]]],


         [[[ 0.,  0.,  0., ...,  0.,  0.,  0.],
           [ 1.,  1.,  0., ...,  0.,  0.,  0.],
           [ 0.,  0.,  2., ...,  0.,  0.,  0.],
           ...,
           [ 1.,  0.,  0., ...,  0.,  1.,  0.],
           [ 1.,  0.,  0., ...,  0.,  0.,  1.],
           [ 1.,  0.,  0., ...,  1.,  1.,  0.]],

          [[ 0.,  0.,  0., ...,  0.,  1.,  1.],
           [ 2.,  2.,  0., ...,  0.,  0.,  1.],
           [ 2.,  1.,  1., ...,  0.,  0.,  0.],
           ...,
           [ 0.,  1.,  0., ...,  1.,  0.,  1.],
           [ 1.,  0.,  0., ...,  0.,  1.,  1.],
           [ 0.,  0.,  0., ...,  0.,  0.,  0.]]]]]])
Coordinates:
  * plantbarcode     (plantbarcode) object 'A1' 'A2'
  * tind_s           (tind_s) int64 0 40 60
  * induction frame  (induction frame) object 'Fp' 'Fmp'
  * jobdate          (jobdate) datetime64[ns] 2020-06-01 ... 2020-06-03
Dimensions without coordinates: y, x

我不明白 nan 数组的来源。对我来说也很奇怪,无论在 concat 中使用什么 dim,这对于每个条目(在本例中为 18 个文件)都有一个坐标值,即使它们不是唯一的,但其他 dim 仅显示为唯一值。

如果有人愿意在这里下载一个小数据集,请联系link(对不起链接中的建议,我会尝试提供一个可以即时生成的合成数据集)

【问题讨论】:

  • 原来使用np.expand_dims() 工作流意味着最后一步xr.concat 会生成DataArray 而不是Dataset,因此您无法将其保存到文件或使用xarray.Dataset.filter_by_attrs
  • 如果我在使用 np.expand_dims() 工作流程后使用 dstest.to_dataset(dim='tind_s') 我会得到 ValueError: conflicting sizes for dimension 'tind_s': length 2 on 140 and length 3 on 0

标签: python python-xarray


【解决方案1】:

您的原始代码在arr.expand_dims(dims={'tind_s': tind}) 中包含一个微妙的错误(错字):我猜您想要dim 而不是dims,后者被xarray 解释为一个新的维度标签(请参阅doc)。此外,tind 在这里用作沿新维度创建的元素数量,这可能也不是您想要的。

您的其他尝试(即,在创建 DataArray 之前扩展数据维度)是 IMO 更好的方法,但可以进一步改进。鉴于您在同一个串联维度上有多个标签,我建议您创建一个多索引并将其分配给串联维度,即类似

import numpy as np
import pandas as pd
import xarray as xr


da_list = []
props = []
prop_names = ['experiment', 'plantbarcode', 'tind']

for i in range(10):
    tind = i
    indf = ['Fp', 'Fmp']
    data = np.ones((2, 640, 480)) * i
    
    da = xr.DataArray(
        data=data[None, ...],
        dims=('props', 'frame', 'y', 'x'),
        coords={'frame': indf}
    )

    props.append((f'experiment{i}', i*2, i))
    da_list.append(da)


prop_idx = pd.MultiIndex.from_tuples(props, names=prop_names)

da_concat = xr.concat(da_list, 'props')
da_concat.coords['props'] = prop_idx

给出:

<xarray.DataArray (props: 10, frame: 2, y: 640, x: 480)>
array([[[[0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.],
         ...,
         [0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.]],

        [[0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.],
         ...,
         [0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.]]],


       [[[1., 1., 1., ..., 1., 1., 1.],
         [1., 1., 1., ..., 1., 1., 1.],
         [1., 1., 1., ..., 1., 1., 1.],
...
         [8., 8., 8., ..., 8., 8., 8.],
         [8., 8., 8., ..., 8., 8., 8.],
         [8., 8., 8., ..., 8., 8., 8.]]],


       [[[9., 9., 9., ..., 9., 9., 9.],
         [9., 9., 9., ..., 9., 9., 9.],
         [9., 9., 9., ..., 9., 9., 9.],
         ...,
         [9., 9., 9., ..., 9., 9., 9.],
         [9., 9., 9., ..., 9., 9., 9.],
         [9., 9., 9., ..., 9., 9., 9.]],

        [[9., 9., 9., ..., 9., 9., 9.],
         [9., 9., 9., ..., 9., 9., 9.],
         [9., 9., 9., ..., 9., 9., 9.],
         ...,
         [9., 9., 9., ..., 9., 9., 9.],
         [9., 9., 9., ..., 9., 9., 9.],
         [9., 9., 9., ..., 9., 9., 9.]]]])
Coordinates:
  * frame         (frame) <U3 'Fp' 'Fmp'
  * props         (props) MultiIndex
  - experiment    (props) object 'experiment0' 'experiment1' ... 'experiment9'
  - plantbarcode  (props) int64 0 2 4 6 8 10 12 14 16 18
  - tind          (props) int64 0 1 2 3 4 5 6 7 8 9
Dimensions without coordinates: y, x

【讨论】:

  • 谢谢!但为了保存它,我想我需要一个数据集,对吧?我不清楚如何使它成为一个。文档说数据集是类似字典的数据数组集合。您如何将每个实验的数据数组组合成一个数据集?
  • 将 DataArray 转换为 Dataset 非常简单:da_concat.to_dataset(name="foo").
  • 您也可以直接将 DataArray 保存在磁盘上(它隐含地做了@Ryan 提到的事情)。在这种情况下,您需要一个额外的步骤,因为不支持多索引:da_concat.reset_index('props').to_netcdf('test.nc')。然后重新加载文件:xr.open_dataset('test.nc').set_index(props=['experiment', 'plantbarcode', 'tind']).
【解决方案2】:

我在 xarray 邮件列表上看到了您的问题。很难调试这个问题,因为它很复杂并且取决于您的数据。如果您可以稍微简化一下,或许可以使用合成数据而不是您的数据文件,那就太好了——请参阅https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports 以获取有关此方面的建议。

如果您分享 print(arr) 的输出也会很有帮助,以便我们了解您的 DataArrays 的内容和结构。

【讨论】:

  • 谢谢。我添加了print(arr) 和一个数据链接,但我会尝试提供一个动态数据集。
  • 它现在更简单了,但主要是独立的。
猜你喜欢
  • 2017-01-30
  • 1970-01-01
  • 2016-09-07
  • 2022-07-25
  • 2020-12-05
  • 2016-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多