Use dimensions from TH2 in a TH1

Dear ROOTers,
I am trying to do sth simple as using one dimension of a TH2 as he dimensions of a TH1.
Let TH2* src=new TH1(“src”, “src”, nBinsX, binArrayX, nBinsY, binArrayY)
TH1* dst=new TH2(“dst”, dst", src->GetNbinsX(), src->GetXaxis()->GetXbins()->GetArray()) does not work as expected. I suspect it has sth to do with overflow/underflow. What is the proper way of achieving this? Obviously the nBinsX, binArrayX are not in the scope of dst, so no way to use them directly. Thanks for any help,
filimon

[quote=“filimon”]Dear ROOTers,
I am trying to do sth simple as using one dimension of a TH2 as he dimensions of a TH1.
Let TH2* src=new TH1(“src”, “src”, nBinsX, binArrayX, nBinsY, binArrayY)
TH1* dst=new TH2(“dst”, dst", src->GetNbinsX(), src->GetXaxis()->GetXbins()->GetArray()) does not work as expected. I suspect it has sth to do with overflow/underflow. What is the proper way of achieving this? Obviously the nBinsX, binArrayX are not in the scope of dst, so no way to use them directly. Thanks for any help,
filimon[/quote]

Can you explain, please - do you want to use bin edges from 2D hist (say along X axis) to create a 1D hist (with these bin edges), or data from 2D hist?
It’s a bit ambiguous in your question.

I am only talking about the “geometry” ie axes/setup of the histogram. Otherwise I would just use a Projection. THe problem seems to be
root [10] invpt->GetNbinsX()
(const Int_t)200
root [11] invpt->GetXaxis()->GetXbins()->GetArray()
(const Double_t*)0x0
So the array is null, is this expected?
filimon

edit: Minimal example reproduction
root [1] TH1* src=new TH1F(“src”, “src”, 10, 0, 10)
root [2] src->GetXaxis()->GetXbins()->GetArray()
(const Double_t*)0x0

[quote=“filimon”]I am only talking about the “geometry” ie axes/setup of the histogram. Otherwise I would just use a Projection. THe problem seems to be
root [10] invpt->GetNbinsX()
(const Int_t)200
root [11] invpt->GetXaxis()->GetXbins()->GetArray()
(const Double_t*)0x0
So the array is null, is this expected?
filimon

edit: Minimal example reproduction
root [1] TH1* src=new TH1F(“src”, “src”, 10, 0, 10)
root [2] src->GetXaxis()->GetXbins()->GetArray()
(const Double_t*)0x0[/quote]

This is a wrong example - you have equidistant edges, it does not work this way. With equidistant edges use min/max and number of bins extracted from TAxis. If you have user-defined bins of variable width, everything is ok:

[code]TH2 *create_2D_hist()
{
const Double_t xEdges[] = {-1., -0.5, 0.5, 1.};
const Int_t nXBins = sizeof(xEdges) / sizeof(Double_t) - 1;

const Double_t yEdges[] = {-2., -1., 0., 1., 2.};
const Int_t nYBins = sizeof(yEdges) / sizeof(Double_t) - 1;

std::cout<<"2D histogram, n X bins: “<<nXBins<<” n Y bins: "<<nYBins<<std::endl;

return new TH2F(“a”, “a”, nXBins, xEdges, nYBins, yEdges);
}

void h2to1()
{
const TH2 * const hist2D = create_2D_hist();
const TAxis * const xAxis = hist2D->GetXaxis();
if (xAxis) {
const Int_t nBins = xAxis->GetNbins();
std::cout<<“got “<<nBins<<” bins along X\n”;

  const TArrayD *edges = xAxis->GetXbins();
  if (edges && edges->GetArray()) {
     TH1F * hist = new TH1F("h1fromh2", "h1fromh2", nBins, edges->GetArray());
     hist->FillRandom("gaus");
     hist->Draw();
  }

}
}
[/code]

No, the example it not wrong at all. Here is a very valid use case:
I take a histogram from a file without knowing anything about its contsturction, I only know that it has two quantities and I am interested in correlating one of them with another histogram I fill myself (using the same binning). With your suggestion my (client code) has to do the conceptual equivalent of
if (hist->HasEquidistandEdges()) { // currently checking whether GetArray==0 for example
Dothis()
}
else {
DoThat()
}
The interface should be consistent regardless of how the the object was contructed. OO design 101. Hence, even if in the equidistant histogram does not actually have internally an array of bins, the function GetArray should return one. I am not certain how this could be solved in an easy way ownership-wise, depending on whether there is anyway internally an Double_t array, whether the histo takes owneship when using the specific ctor (nbins, Double_t bins) and many other things to consider, but the point is that currently the function is not consistent depending on which ctor was used.

Your example is wrong since you are changing conditions - at the beginning you’ve asked about variable-sized bins, suddenly, you want to use equidistant. If it’s difficult for you to add one simple condition in your code - I probably can not help you.

Personally, I see absolutely NO REASON to spend time calculating/recalculating writing/rewriting these numbers if they can be count “on the fly” using a trivial “formula”. If you want - please, submit a feature request in JIRA, but I do not think your request is reasonable - for me it’s totally logical and consistent that you can request bins only if you provided bins when creating a histogram. I’m pretty sure, there are users who do not want to pay for the sake of “interface should be consistent regardless of how blah blah blah bla…”. What you see as consistent - is inconsistent for me - it’s like you’ve created a TF1 object using expression like “sin(x) + 10” and suddenly you want TF1 object to return you a pointer to C/C++ function double (*pf)(double) doing something like:

double fun(double x) { return std::sin(x) + 10; }

just because TF1 can be created using pointer to function.

Best regards.

Is there a pointer on the exact place on the code on how are the bins internally calculated in both cases during construction and/or filling (including underflow/overflow allocation and handling which is not “trivial” at all but completely implemantation-dependent)? I need this for something completely irrelavant and we might as well get something useful out of this post since the interface will remain “consistent” as it is (for some value of consistent) :slight_smile:

Hm, let’s start from a ctors (even with line numbers!):
//equidistant:
619 TH1::TH1(const char *name,const char *title,Int_t nbins,Double_t xlow,Double_t xup) (TH1.cxx):

641 if (nbins <= 0) {Warning("TH1","nbins is <=0 - set to nbins = 1"); nbins = 1; } 642 fXaxis.Set(nbins,xlow,xup); 643 fNcells = fXaxis.GetNbins()+2;

//variable-size:
TH1::TH1(const char *name,const char *title,Int_t nbins,const Float_t *xbins) (TH1.cxx):

Build(); 664 if (nbins <= 0) {Warning("TH1","nbins is <=0 - set to nbins = 1"); nbins = 1; } 665 if (xbins) fXaxis.Set(nbins,xbins); 666 else fXaxis.Set(nbins,0,1); 667 fNcells = fXaxis.GetNbins()+2;

Ok, TAxis then:

663 void TAxis::Set(Int_t nbins, Double_t xlow, Double_t xup) 664 { 665 // Initialize axis with fix bins 666 667 fNbins = nbins; 668 fXmin = xlow; 669 fXmax = xup; 670 if (!fParent) SetDefaults(); 671 if (fXbins.fN > 0) fXbins.Set(0); 672 }

and

674 //______________________________________________________________________________ 675 void TAxis::Set(Int_t nbins, const Float_t *xbins) 676 { 677 // Initialize axis with variable bins 678 679 Int_t bin; 680 fNbins = nbins; 681 fXbins.Set(fNbins+1); 682 for (bin=0; bin<= fNbins; bin++) 683 fXbins.fArray[bin] = xbins[bin]; 684 for (bin=1; bin<= fNbins; bin++) 685 if (fXbins.fArray[bin] < fXbins.fArray[bin-1]) 686 Error("TAxis::Set", "bins must be in increasing order"); 687 fXmin = fXbins.fArray[0]; 688 fXmax = fXbins.fArray[fNbins]; 689 if (!fParent) SetDefaults(); 690 }

But this is not really interesting, let’s have a look at, say, Fill (since we’ll have to find a bin) in TH1.cxx:

3002 Int_t TH1::Fill(Double_t x) 3003 { 3004 // Increment bin with abscissa X by 1. 3005 // 3006 // if x is less than the low-edge of the first bin, the Underflow bin is incremented 3007 // if x is greater than the upper edge of last bin, the Overflow bin is incremented 3008 // 3009 // If the storage of the sum of squares of weights has been triggered, 3010 // via the function Sumw2, then the sum of the squares of weights is incremented 3011 // by 1 in the bin corresponding to x. 3012 // 3013 // The function returns the corresponding bin number which has its content incremented by 1 3014 3015 if (fBuffer) return BufferFill(x,1); 3016 3017 Int_t bin; 3018 fEntries++; 3019 [b] bin =fXaxis.FindBin(x);[/b] 3020 if (bin <0) return -1; 3021 AddBinContent(bin); 3022 if (fSumw2.fN) ++fSumw2.fArray[bin]; 3023 if (bin == 0 || bin > fXaxis.GetNbins()) { 3024 if (!fgStatOverflows) return -1; 3025 } 3026 ++fTsumw; 3027 ++fTsumw2; 3028 fTsumwx += x; 3029 fTsumwx2 += x*x; 3030 return bin; 3031 }

And again, TAxis.cxx:

249 //______________________________________________________________________________ 250 Int_t TAxis::FindBin(Double_t x) 251 { 252 // Find bin number corresponding to abscissa x. NOTE: this method does not work with alphanumeric bins !!! 253 // 254 // If x is underflow or overflow, attempt to extend the axis if TAxis::kCanExtend is true. Otherwise, return 0 or fNbins+1. 255 256 Int_t bin; 257 // NOTE: This should not be allowed for Alphanumeric histograms, but it is heavily used (legacy) in the TTreePlayer to fill alphanumeric histograms. 258 if (IsAlphanumeric() && gDebug) Info("FindBin","Numeric query on alphanumeric axis - Sorting the bins or extending the axes / rebinning can alter the correspondence between the label and the bin interval."); 259 if (x < fXmin) { //*-* underflow 260 bin = 0; 261 if (fParent == 0) return bin; 262 if (!CanExtend()) return bin; 263 ((TH1*)fParent)->ExtendAxis(x,this); 264 return FindFixBin(x); 265 } else if ( !(x < fXmax)) { //*-* overflow (note the way to catch NaN) 266 bin = fNbins+1; 267 if (fParent == 0) return bin; 268 if (!CanExtend()) return bin; 269 ((TH1*)fParent)->ExtendAxis(x,this); 270 return FindFixBin(x); 271 } else { 272 if (!fXbins.fN) { //*-* fix bins 273 bin =[b] 1 + int (fNbins*(x-fXmin)/(fXmax-fXmin) );[/b] 274 } else { //*-* variable bin sizes 275 //for (bin =1; x >= fXbins.fArray[bin]; bin++); 276 bin = [b]1 + TMath::BinarySearch(fXbins.fN,fXbins.fArray,x);[/b] 277 } 278 } 279 return bin; 280 }

P.S. unfortunately, tags do not work inside :frowning: