【问题标题】:Copy TTree to Other File将 TTree 复制到其他文件
【发布时间】:2023-01-13 21:30:59
【问题描述】:

我正在尝试从一个根文件中提取周期/修订(“TreeName;3”等),并在新的文件中使它们成为自己的树。我尝试通过创建一个新文件并将其分配给一个新名称来做到这一点,但是我收到一条错误消息,告诉我 TTree 不可写

with uproot.open("old_file.root") as in_file:
    with uproot.recreate("new_file.root") as out_file:
        for key in in_file.keys():
            ttree = in_file[key]
            new_name = key.replace(";","_")
            out_file[new_name] = ttree

这导致NotImplementedError: this ROOT type is not writable: TTree 我有点困惑,因为当我打印出 out_file 时,它​​告诉我它是一个 <WritableDirectory '/' ...> 我希望它按值将 out_file[new_name] 分配给 ttree。然而深入研究文档“uproot.writing.identify.add_to_directory”说如果要添加的对象不可写,它会引发此错误,所以我猜它不会像我预期的那样在内存中复制。

接下来,我尝试先创建一棵新树,然后逐块移动数据。然而,这也没有用,因为树创建失败:

out_file[new_name] = ttree.typenames()

ValueError: 'extend' must fill every branch with the same number of entries; 'name2' has 7 entries 类型名称类似于{'name1': 'double', 'name2': 'int32_t', 'name3': 'double[]', 'name4': 'int32_t[]', 'name5': 'bool[]'}

尝试调试它我注意到一些非常奇怪的行为

out_file[new_name] = {'name1': 'double', 'name2': 'float32'}

产生完全相同的错误,而

out_file[new_name] = {'name1': 'float64', 'name2': 'float32'}
out_file[new_name].show()

name                 | typename                 | interpretation                
---------------------+--------------------------+-------------------------------
name1                | uint8_t                  | AsDtype('uint8')
name2                | uint8_t                  | AsDtype('uint8')

所以在这一点上我不知道数据类型是什么了

最后我尝试通过编写数组来做到这一点,但这也失败了

arrays = ttree.arrays(ttree.keys(),library='np')
out_file[key.replace(";","_")] = arrays

给予TypeError: cannot write Awkward Array type to ROOT file: unknown

使用笨拙的数组或熊猫出现类似问题

【问题讨论】:

  • 在 Uproot 中没有将整个 TTrees 从一个文件复制到另一个文件的工具,但也许应该有,因为这个问题已经被问过几次了。由于没有任何“复制 TTree”实现,您必须将其读入数组(如有必要,逐块读入)并将其写回,就像您一直在尝试做的那样。
  • typename 是 C++ 类型; TTree 初始化 (mktree) 采用的类型是 NumPy 或 Awkward 类型。 (我没想到有人会尝试在那里使用 C++ typename,但这是一个很好的考虑。)所以 np.float64 是合法的,"float64" 是合法的,"var * float64"(对于参差不齐的数组)是合法的,但 "double""double[]" 不是。
  • out_file[new_name] = {"name1": array1, "name2": array2} 语法采用数组作为字典的值,而不是类型名称。如果你想在用WritableTree.extend填充之前分配一个TTree,请参阅WritableDirectory.mktree。在您的情况下,'float64' 被解释为数组本身,即 7 个​​ uint8 值(字符串中的字符)。这也是出乎意料的,应该避免。
  • 非常感谢那些 cmets @JimPivarski!现在对我来说更有意义了:)

标签: python root-framework uproot


【解决方案1】:

我决定给出一个完整的工作示例(跟进上面的 cmets),但发现有很多选择要做。你只想复制输入的 TTree——你不想做出选择——所以你真的想要一个高级的“复制整个 TTree”功能,但这样的功能并不存在。 (这将是对 Uproot 的一个很好的补充,或者是一个使用 Uproot 来完成hadd 类型工作的新模块。如果有人感兴趣的话,这是一个很好的项目!)

我从 this file 开始,可以通过多种方式获得:

file_path = "root://eospublic.cern.ch//eos/opendata/cms/derived-data/AOD2NanoAODOutreachTool/Run2012BC_DoubleMuParked_Muons.root"

file_path = "http://opendata.cern.ch/record/12341/files/Run2012BC_DoubleMuParked_Muons.root"

file_path = "/tmp/Run2012BC_DoubleMuParked_Muons.root"

它足够大,应该分块复制,而不是一次全部复制。第一个块设置类型,因此可以通过将新分支名称分配给数组来执行,但后续块必须调用WritableFile.extend,因为你不想替换新的 TTree,你想添加它。这些都没有明确处理类型;类型是从数组中提取的。

这是第一次尝试,使用 "100 MB" 作为块大小。 (这将是输出 TTree 中跨 TBranches 的 TBasket 大小的总和。我们在这里所做的不仅仅是复制;它是将数据重新分区为新的块大小。)

with uproot.recreate("/tmp/output.root") as output_file:
    first_chunk = True

    with uproot.open(file_path) as input_file:
        input_ttree = input_file["Events"]

        for arrays_chunk in input_ttree.iterate(step_size="100 MB"):
            if first_chunk:
                output_file["Events"] = arrays_chunk
                first_chunk = False
            else:
                output_file["Events"].extend(arrays_chunk)

但是,它失败了,因为赋值和 extend 期望数组字典,而不是单个数组。

所以我们可以请求 TTree.iterate 给我们一个 Awkward Arrays 的字典,每个 TBranch 一个,而不是一个代表所有 TBranches 的 Awkward Array。看起来像这样:

with uproot.recreate("/tmp/output.root") as output_file:
    first_chunk = True

    with uproot.open(file_path) as input_file:
        input_ttree = input_file["Events"]

        for dict_of_arrays in input_ttree.iterate(step_size="100 MB", how=dict):
            if first_chunk:
                output_file["Events"] = dict_of_arrays
                first_chunk = False
            else:
                output_file["Events"].extend(dict_of_arrays)

它复制文件,但是原始文件有 TBranches 之类的

name                 | typename                 | interpretation                
---------------------+--------------------------+-------------------------------
nMuon                | uint32_t                 | AsDtype('>u4')
Muon_pt              | float[]                  | AsJagged(AsDtype('>f4'))
Muon_eta             | float[]                  | AsJagged(AsDtype('>f4'))
Muon_phi             | float[]                  | AsJagged(AsDtype('>f4'))
Muon_mass            | float[]                  | AsJagged(AsDtype('>f4'))
Muon_charge          | int32_t[]                | AsJagged(AsDtype('>i4'))

新文件有 TBranches 之类的

name                 | typename                 | interpretation                
---------------------+--------------------------+-------------------------------
nMuon                | uint32_t                 | AsDtype('>u4')
nMuon_pt             | int32_t                  | AsDtype('>i4')
Muon_pt              | float[]                  | AsJagged(AsDtype('>f4'))
nMuon_eta            | int32_t                  | AsDtype('>i4')
Muon_eta             | float[]                  | AsJagged(AsDtype('>f4'))
nMuon_phi            | int32_t                  | AsDtype('>i4')
Muon_phi             | float[]                  | AsJagged(AsDtype('>f4'))
nMuon_mass           | int32_t                  | AsDtype('>i4')
Muon_mass            | float[]                  | AsJagged(AsDtype('>f4'))
nMuon_charge         | int32_t                  | AsDtype('>i4')
Muon_charge          | int32_t[]                | AsJagged(AsDtype('>i4'))

发生的事情是 Uproot 不知道每个尴尬数组的每个条目都有相同数量的项目(一个事件中 pt 值的数量与一个事件中 eta 值的数量相同) .如果 T 分支不是全部都是 μ 介子,而是有些是 μ 介子,有些是电子或喷流,那就不是真的了。

这些 nMuon_ptnMuon_eta 等 TBranches 被包含在内的原因是因为 ROOT 需要它们。 Muon_ptMuon_eta 等。TBranches 在 ROOT 中被读取为可变长度的 C++ 数组,C++ 用户需要知道预分配一个数组有多大,以及在哪个数组条目之后内容是未初始化的垃圾。 Python 中不需要这些(Awkward Array 会阻止用户看到未初始化的垃圾)。

所以你可以忽略它们。但是如果你真的需要/想要摆脱它们,这里有一个方法:构建你想要写的数组。现在我们正在处理类型,我们将使用WritableDirectory.mktree 并明确指定类型。由于每次写入都是extend,我们将不必再跟踪我们是否正在写入first_chunk 或后续块。

对于Muon_ptMuon_eta等TBranch共享一个计数器TBranch,nMuons,你希望Muon字段是一个可变长度的μ子对象列表数组,pteta等领域。该类型可以从字符串构造:

import awkward as ak

muons_type = ak.types.from_datashape("""var * {
    pt: float32,
    eta: float32,
    phi: float32,
    mass: float32,
    charge: int32
}""", highlevel=False)

给定 chunk 类型为 var * float32 的分隔数组,您可以创建类型为 var * {pt: float32, eta: float32, ...}ak.zip 的单个数组。

muons = ak.zip({
    "pt": chunk["Muon_pt"],
    "eta": chunk["Muon_eta"],
    "phi": chunk["Muon_phi"],
    "mass": chunk["Muon_mass"],
    "charge": chunk["Muon_charge"],
})

(打印muons.type 返回类型字符串。)这是您可能用于数据分析的表单。假设用户会将数据作为读取和写入之间的对象进行分析,而不是从一个文件中读取并在不进行任何修改的情况下写入另一个文件。

这是一个读写器,使用muons_type

with uproot.recreate("/tmp/output.root") as output_file:
    output_ttree = output_file.mktree("Events", {"Muon": muons_type})

    with uproot.open(file_path) as input_file:
        input_ttree = input_file["Events"]

        for chunk in input_ttree.iterate(step_size="100 MB"):
            muons = ak.zip({
                "pt": chunk["Muon_pt"],
                "eta": chunk["Muon_eta"],
                "phi": chunk["Muon_phi"],
                "mass": chunk["Muon_mass"],
                "charge": chunk["Muon_charge"],
            })

            output_ttree.extend({"Muon": muons})

或者您可以通过再次跟踪 first_chunk 来显式构造 muons_type 来完成它:

with uproot.recreate("/tmp/output.root") as output_file:
    first_chunk = True

    with uproot.open(file_path) as input_file:
        input_ttree = input_file["Events"]

        for chunk in input_ttree.iterate(step_size="100 MB"):
            muons = ak.zip({
                "pt": chunk["Muon_pt"],
                "eta": chunk["Muon_eta"],
                "phi": chunk["Muon_phi"],
                "mass": chunk["Muon_mass"],
                "charge": chunk["Muon_charge"],
            })

            if first_chunk:
                output_file["Events"] = {"Muon": muons}
                first_chunk = False
            else:
                output_file["Events"].extend({"Muon": muons})

诚然,它很复杂(因为我展示了很多备选方案,各有优缺点),但那是因为不加修改地复制 TTree 并不是 TTree 编写函数的可预见用例。由于这是一个重要的用例,因此隐藏这些细节的专门功能将是一个受欢迎的补充。

【讨论】:

  • 另一个复杂性来源是 ROOT TTrees 不能完美地映射到 Awkward Array 类型(因此,计数器 TBranch 等等)。 Arrow 和 Parquet 格式更匹配;见ak.to_parquetak.from_parquet
  • 我花了一些时间来处理和尝试所有这些选项,但感谢您进行了如此详细的介绍并很好地解释了所有内容:) 对我来说另一个复杂的问题是我不知道树上有哪些分支或什么它们的数据类型。所以我以 muons_type 选项为例,我需要将类型字符串从 c++ 处理为 numpy,这有点尴尬。
  • Type 对象可以作为一棵不可变树来操作——您可以将内容拉出并通过将内容传递给它们的构造函数来构造新的内容——因此不需要通过字符串来操作它。 (对象构造通常比字符串操作更健壮;至少,错误更容易理解。)仅供参考,以防万一。
猜你喜欢
  • 2014-04-05
  • 2011-11-19
  • 1970-01-01
  • 1970-01-01
  • 2020-07-31
  • 1970-01-01
  • 1970-01-01
  • 2019-05-05
  • 1970-01-01
相关资源
最近更新 更多