Oprócz histogramów 1-wymiarowych ROOT dysponuje również histogramami 2 i 3 wymiarowymi. Jeśli chodzi o więcej wymiarów to na pomoc przychodzi klasa THnSparse ale osobiście nigdy jej nie używałem więc pominę ją w tym tutorialu. Wynika to z tego, że wraz z liczba wymiarów rośnie szybko rozmiar histogramów, a w większości wypadków i tak większość binów jest pusta.
Histogramy 2D
Reprezentowane przez klasy typu TH2*, i tak np. TH2D reprezentuje 2 wymiarowy histogram z liczbami typu double. Rozważmy sytuację, gdy chcemy wypełnić histogram dwuwymiarowym gausem. Możemy tu użyć funkcji TF2 - reprezentuje ona funkcję dwuwymiarową (o funkcjach będzie więcej przy okazji omówienia fitowania). Przy jej pomocy wypełniamy histogram 2D. Kod przykładu znajduje się poniżej.
Double_t dwugaus(Double_t *x, Double_t *par){ return par[0]*(TMath::Gaus(x[0],par[1],par[2])+TMath::Gaus(x[1],par[1],par[2])); } void dwuwymiar(){
gStyle->SetPalette(kRainBow); TCanvas *c1 = new TCanvas("c1","Graph Example",200,10,700,500); TH2D *histogram = new TH2D("name","title",100,-1.0,1.0,100,-1.0,1.0); TF2 *funkcja = new TF2("gausowa",dwugaus,-1.0,1.0,-1.0,1.0,3); funkcja->SetParameter(0,2.0); //wartości średnie gausów funkcja->SetParameter(1,0.0); //sigma gausów funkcja->SetParameter(2,0.5); for(Int_t i=1;i<=GetNbinsX();i++){ for(Int_t j=1;j<=GetNbinsY();j++){ Double_t xval = histogram->GetXaxis()->GetBinCenter(i); Double_t yval = histogram->GetYaxis()->GetBinCenter(j); histogram->SetBinContent(i,j,funkcja->Eval(xval,yval)); } } histogram->Draw("lego2"); }
W kodzie powyżej tworzymy funkcję dwugaus, ona jest standardową funkcją, która jest przekazywana to TF2. TF2 symbolizuje "matematyczna funkcję dwuwymiarową". W konstruktorze TF2 podajmy jako jeden z argumentów nazwę funkcji "programistycznej" (tj. dwugaus). Funkcja "programistyczna" zwraca wartość w danym punkcie, a jako parametry przyjmuje dwa wskaźniki. Funkcja programistyczna zawsze ma dwa wskaźniki - pierwszy to tablica argumentów funkcji (tutaj x[0] = to nasz x a x[1] to nasz y), drugi argument to tablica parametrów (par[0] to pierwszy parametr, par[1] to drugi itd.).
Następnie ustawiamy parametry naszej funkcji i robimy pętlę po binach, Eval zwraca wartość funkcji. Na końcu zaś rysujemy histogram. gStyle->SetPalette odpowiada za "styl kolorystyczny" rysowanych histogramów.
Rys.1 Histogram 2-wymiarowy wypełniony funkcją gaussa.
Histogramy 3D
Są one reprezentowane przez klasy typu TH3*. I tak TH3D reprezentuje histogram ze zmiennymi typu double. Przykład makra tworzącego taki histogram poniżej:
Double_t trojgaus(Double_t *x, Double_t *par){ return par[0]*(TMath::Gaus(x[0],par[1],par[2])+TMath::Gaus(x[1],par[1],par[2]) +TMath::Gaus(x[2],par[1],par[2])); } void trojwymiar(){ gStyle->SetCanvasPreferGL(true); TCanvas *c1 = new TCanvas("c1","Graph Example",200,10,700,500); TH3D *histogram = new TH3D("name","title",50,-1.0,1.0,50,-1.0,1.0,50,-1.0,1.0); TF3 *funkcja = new TF3("gausowa",trojgaus,-1.0,1.0,-1.0,1.0,-1.0,1.0,3); funkcja->SetParameter(0,2.0); //wartości średnie gausów funkcja->SetParameter(1,0.0); //sigma gausów funkcja->SetParameter(2,0.5); for(Int_t i=0;iGetNbinsX();i++){ for(Int_t j=0;jGetNbinsY();j++){ for(Int_t k=0;kGetNbinsZ();k++){ Double_t xval = histogram->GetXaxis()->GetBinCenter(i); Double_t yval = histogram->GetYaxis()->GetBinCenter(j); Double_t zval = histogram->GetZaxis()->GetBinCenter(k); histogram->SetBinContent(i,j,k,funkcja->Eval(xval,yval,zval)); } } } histogram->Draw("glcol"); }
Jak widać kod wygląda bardzo podobnie do histogramu 2D z tą różnicą że włączono używanie OpenGL w rysowaniu histogramu (linia 6).
Rys. 2. Histogram 3D z OpenGL.
Można również rysować histogram 3D z użyciem "brył".
// Display a 3D histogram using GL (box option). //Author: Timur Pocheptsov void glh3c() { gStyle->SetCanvasPreferGL(kTRUE); TGLTH3Composition * comp = new TGLTH3Composition(); TH3F * h1 = new TH3F("h1", "h1", 10, -1., 1., 10, -1., 1., 10, -1., 1.); h1->SetFillColor(kRed); TH3F * h2 = new TH3F("h2", "h2", 10, -1., 1., 10, -1., 1., 10, -1., 1.); h2->SetFillColor(kGreen); TH3F * h3 = new TH3F("h3", "h3", 10, -1., 1., 10, -1., 1., 10, -1., 1.); for(int i=0;i<10000;i++){ h1->Fill(gRandom->Gaus(0,1), gRandom->Gaus(0,1), gRandom->Gaus(0,1)); h2->Fill(gRandom->Gaus(0.5,1), gRandom->Gaus(0.5,1), gRandom->Gaus(0.5,1)); h3->Fill(gRandom->Gaus(-0.5,1), gRandom->Gaus(-0.5,1), gRandom->Gaus(-.5,1)); } h3->SetFillColor(kBlue); comp->AddTH3(h1); comp->AddTH3(h2, TGLTH3Composition::kSphere); comp->AddTH3(h3); comp->Draw(); TPaveLabel *title = new TPaveLabel(0.04, 0.86, 0.96, 0.98, "TH3 composition."); title->SetFillColor(32); title->Draw(); }
Rys. 3 Histogram 3D "z bryłami".
Projekcje
Ważną operacją przy operowaniu na histogramach są tzw. projekcje czyli umiejętność "wycięcia" części histogramu. Przykładowo poniżej pokazano jak rysować "plasterki 1-wymiarowe z dwuwymiarowego histogramu".
Double_t dwugaus(Double_t *x, Double_t *par){ return par[0]*(TMath::Gaus(x[0],par[1],par[2])+TMath::Gaus(x[1],par[1],par[2])); } void Tomograf(){ TCanvas *c1 = new TCanvas("c1","Graph Example",200,10,700,500); c1->Divide(2,1); TH2D *histogram = new TH2D("name","title",100,-1.0,1.0,100,-1.0,1.0); TH1D *slice = new TH1D("skan","skan",100,-1,1); TF2 *funkcja = new TF2("gausowa",dwugaus,-1.0,1.0,-1.0,1.0,3); funkcja->SetParameter(0,2.0); //wartości średnie gausów funkcja->SetParameter(1,0.0); //sigma gausów funkcja->SetParameter(2,0.5); for(Int_t i=0;iGetNbinsX();i++){ for(Int_t j=0;jGetNbinsY();j++){ Double_t xval = histogram->GetXaxis()->GetBinCenter(i); Double_t yval = histogram->GetYaxis()->GetBinCenter(j); histogram->SetBinContent(i,j,funkcja->Eval(xval,yval)); } } c1->cd(1); histogram->Draw("lego2"); c1->cd(2); for(Int_t i=0;iGetNbinsY();i++){ slice=histogram->ProjectionX("projekcja",i,i+1); slice->Draw(); c1->Update(); //czekaj 100 ms między projekcjami gSystem->Sleep(100); } }
Najważniejszą częścią jest metoda TH2D::ProjectionX która pobiera projekcję. Należy tutaj zwrócić uwagę na dwie ważne rzeczy:
- projekcja jest pobierana na podstawie numeru binu a nie wartości na osi X
- projekcja powinna mieć unikalną nazwę jeśli chcemy rysować kilka projekcji, inaczej ostatnia o danej nazwie nadpisuje wszystkie poprzednie o tej samej nazwie
Rys. 4. Histogram 3D z animacją projekcji.
Jednoczesne rysowanie histogramów
Kolejnym problemem jest rysowanie kilku histogramów obok siebie. Poniżej rysunek i makro próbujące narysować dwa TH2D razem:
void fill(TH2D *h1,TH2D *h2){ for(Int_t i=1;iGetNbinsX();i++){ for(Int_t j=1;jGetNbinsX();j++){ if((i<=20&&j<20)||(i>20&&j>20)){ h1->SetBinContent(i,j,1); }else{ h2->SetBinContent(i,j,1); } } } } void two_dim(){ TCanvas *c1 = new TCanvas(); c1->Divide(1,2); TH2D *h1 = new TH2D("h1","h1",40,0,1,40,0,1); h1->SetFillColor(kRed); TH2D *h2 = new TH2D("h2","h2",40,0,1,40,0,1); h2->SetFillColor(kWhite); fill(h1,h2); c1->cd(1); h1->Draw("lego1"); h2->Draw("SAME+lego1"); c1->cd(2); THStack *stack = new THStack(); stack->Add(h1); stack->Add(h2); stack->Draw(); }
Rys. 5. Histogramy 2D rysowane razem
W makrze podjęto próbę rysowania dwóch histogramów (białego i czerwonego), po zsumowaniu powinny dać biało-czerwoną szachownicę. Gdy użyto metody "SAME" (górny pad) wyraźnie widać że "dolna ćwiartka" nie jest wypełniona, nie jest wypełniona również górna ćwiartka - tak naprawdę czerwony obszar rysowany na górze pochodzi od "ścianek" histogramu, a nie od wypełnienia. Oznacza to, że w przypadku rysowania metodą "SAME" dla dwóch wymiarów drugi (i kolejne) histogramy nie są tak naprawdę rysowane.
Na dolnym padzie użyto metody z użyciem THSTack, dzięki temu narysowana jest suma histogramów.
Należy tu zwrócić uwagę że THStack nie jest idealnym odpowiednikiem metody "SAME", THStack został zaprojektowany głównie do rysowania "zsumowanych histogramów", a nie histogramów nałożonych na siebie tak jak to robi "SAME". Najłatwiej to wyjaśnić na poniższym makrze i rysunku gdzie wyraźnie widać że THSTack to suma histogramów a nie to samo co SAME.
Rys. 6 Histogramy TH1D narysowane metodą "SAME" (u góry) oraz z użyciem THStack. Poniżej makro pozwalające uzyskać te obrazki.
void generator(TH1D *h, Double_t mean, Double_t sigma, Int_t times){ for(Int_t i =0;i<times;i++){ h->Fill(gRandom->Gaus(mean,sigma)); } } void one_dim(){ TCanvas *c1 = new TCanvas(); c1->Divide(1,2); c1->cd(1); TH1D *h1 = new TH1D("h1","h1",100,0,0.5); h1->SetFillColor(kRed); TH1D *h2 = new TH1D("h2","h2",100,0,0.5); h2->SetFillColor(kGreen); generator(h1,0.2,0.1,2000); generator(h2,0.25,0.1,2000); h1->Draw(); h2->Draw("SAME"); c1->cd(2); THStack *stack = new THStack(); stack->Add(h1); stack->Add(h2); stack->Draw(); }