【ROOT from CERN】——TChain读取多个.root文件

TChain可以完成具有相同结构(指存储的信息相同)的树的串联读取,也可以完成具有相同事例数(如果事例数不相同可能会导致数据错位)的不同结构的树的并联读取

一、TChain的基本介绍

1、TChain::Add()的串联读取

在存储大量数据时,考虑到计算机系统的限制(如内存等)可能会考虑将一份条件相同的数据拆分后进行存储。这样就会形成多份内部格式和数据结构相同的.root文件,其在后续处理时则需要一并处理。依经验之谈,可以用TFile指针加循环完成读取。但是当各个文件命名不够规范的时候,就会造成更严重的麻烦。对此,ROOT提供了TChain类,同时搭配使用通配符和循环脚本可以完成该目的。

在此之前,需要您去看一下《到底什么是Tree》这篇文章,接下来要以此为基础继续讲下去。假设我们拥有文件DAT000001.root(含有n个事例)和DAT000002.root(含有m个事例),其具有结构相同的Tree名为“Particle”,则可以使用如下代码:

TChain ch("Particle");
ch.Add("DAT000001.root");
ch.Add("DAT000002.root");

//Poniter form
TChain* ch = new TChain("Particle");
ch->Add("DAT000001.root");
ch->Add("DAT000002.root");

TChain::Add()方法对于Tree的操作如下图,其导致整个Tree结构的延长,类似一种串联我也就这么叫了,比较好理解,形成的链表将具有n+m个事例:

 2、TChain::AddFriend()的并联读取

如前文所述,TChain::AddFriend()方法的形式相信读者也能够猜到。如果在数据存储时,将多个事例的不同数据参量分开存储,就用到接下来的方法。

假设我们拥有文件DAT000001.root(含有n个事例)和DAT000002.root(含有m个事例),其具有结构相同的Tree名为“Particle”。同时,我们拥有文件DAT000011.root(含有n个事例)和DAT000012.root(含有m个事例),其具有结构相同的Tree名为“Fitting”。我们的目的是将结构相同的两者分别串联后再并联,则可以使用如下代码(还未测试,应该没问题):

TChain ch1("Particle");
ch.Add("DAT000001.root");
ch.Add("DAT000002.root");

TChain ch2("Fitting");
ch.Add("DAT000011.root");
ch.Add("DAT000012.root");

ch1.AddFriend("Fitting");

//Poniter form
TChain* ch1 = new TChain("Particle");
ch1->Add("DAT000001.root");
ch1->Add("DAT000002.root");

TChain* ch2 = new TChain("Fitting");
ch2->Add("DAT000011.root");
ch2->Add("DAT000012.root");

ch1->AddFriend("Fitting");

TChain::AddFriend()方法对于Tree的操作如下图(需和上文的图一并对比),其导致整个Tree结构的延长(当串联文件只有一件时则不延长)和拓宽,其主要功能是后者,这类似一种并联,形成的链表将被拓宽: 

如果仅想要拓宽树的宽度,则没有必要使用TChain类,选择使用TTree::AddFriend()方法即可,在这里我引用一段官网的代码,读者可以注意一下官方教程的代码风格。

void treeWithFriend() {
   std::unique_ptr<TFile> myFile( TFile::Open("file.root") );
   auto tree = myFile->Get<TTree>("TreeName");

   std::unique_ptr<TFile> myFriendFile( TFile::Open("friend.root") );
   auto friendTree = myFriendFile->Get<TTree>("FriendTreeName");

   tree->AddFriend(friendTree);

   int variable;
   tree->SetBranchAddress("branchName", &variable);
   int variableFriend;
   tree->SetBranchAddress("FriendTreeName.friendBranchName", &variableFriend);

   // Iteration over `tree` automatically advances its friend trees.
   for (int iEntry = 0; tree->LoadTree(iEntry) >= 0; ++iEntry) {
      // Load the data for the given tree entry
      tree->GetEntry(iEntry);

      printf("%d %d\n", variable, variableFriend);
   }

二、TChain读取文件

1、TChain的读取模板

简单总结如何用TChain读取.root中Tree的数据:

1、定义a个TChain指针(a由做多少次拓宽决定,如果仅是延长Tree则a=1)
2、将相同结构的.root文件利用Add()串联进入同一个Chain
3、将不同结构的.root文件利用AddFriend()并联至一个Chain(如果没有,跳过本步骤)
4、使用TChain的GetEntries()获取事例总数n
5、使用SetBranchAddress将Branch地址与自设变量相连
6、使用循环令GetEntry()或GetEvent()从0到n-1进行遍历获取数据

代码模板如下,注意一些字符串变量的细节:

{
    Int_t variable,variableFriend;//just a example
 
    TChain* ch1 = new TChain("TreeName");
    ch1->Add("filename1.root");
    ch1->Add("filename2.root");

    TChain* ch2 = new TChain("FriendTreeName");
    ch2->Add("friendname1.root");
    ch2->Add("friendname2.root");
    ch1->AddFriend("FriendTreeName");//If there is no FriendChain,ignore this part above

    Long64_t entries = ch1->GetEntries();

    ch1->SetBranchAddress("branchName", &variable);
    ch1->SetBranchAddress("FriendTreeName.friendBranchName", &variableFriend);//same as above
 
    for(Int_t i=0;i<entries;++i)
    {
        ch1->GetEntry(i);
        //handle
    }
}

2、如何正确使用通配符

在以上的例子中,我们仅用了较少的例子,如果存在数百份.root文件,自然不可能一个个地添加到Chain中,ROOT对此提供了通配符来解决这个问题。TChian允许传入一个符合通配符规则的字符串。

TChain* ch = new TChain("TreeName");
ch->Add("/home/username/data/*.root");

该代码将/home/username/data路径下所有以.root为结尾的文件添加进入Chain。

如果您想要配合bash使用该语法,请注意通配符的规则。我们将如上代码编入文件ChainTest.C并使用g++编译的可执行文件(如果您不知道如何使用,请查看《生成ROOT可执行文件》),同时希望使其传入的第一个参数为目标路径,那么您应该:

#include <iostream>
#include <string>
#include <cstring>
#include "TH2F.h"
#include "TROOT.h"
#include "TFile.h"
#include "TTree.h"
#include "TChain.h"
using namespace std;

Int_t main(Int_t argc , Char_t** argv)
{
    TChain *ch = new TChain("TreeName");
    string dirname = argv[1];
	string filename = dirname  + "*.root";//pay attention to this part
	ch->Add(filename.c_str());

    //data reading part above

	return 0;
}

您应该在shell中这样输入:

./ChainTest /home/username/data/

而不是:

#include <iostream>
#include <string>
#include <cstring>
#include "TH2F.h"
#include "TROOT.h"
#include "TFile.h"
#include "TTree.h"
#include "TChain.h"
using namespace std;

Int_t main(Int_t argc , Char_t** argv)
{
    TChain *ch = new TChain("TreeName");
	ch->Add(argv[1]);

    //data reading part above

	return 0;
}

shell中: 

./ChainTest /home/username/data/*.root

注意,上述提到的第二种做法是错误的 。在shell中直接输入通配符将会使该参量被传入程序前就会被先行替换为若干符合条件的字符串。假如该文件夹有DAT000001.root和DAT000002.root,那么上述语句在传入程序前就会变成:

./ChainTest /home/username/data/DAT000001.root /home/username/data/DAT000002.root

该做法会导致TChain只能接收到第一个文件名,这并不是我们想要的结果。

三、关于有类列表的root

以上提及的TChain都用于具有简单的用户定义的结构的Tree的读取,如果目标Tree是以类为结构存入的,那么将会导致读取的错误,对于这种情况,在以后会介绍一个MakeClass()的方法,来解决这一种读取困难。

【资料】

1、ROOT官网——ROOT: analyzing petabytes of data, scientifically. - ROOT 

2、ROOT教程——Trees - ROOT

3、ROOT官方指导书——《ROOTUsersGuide》

如有错误请指正。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值