Selecting data with specific qualities
Learn how to filter data and make partitions
Selecting rows of a table which fullfil given requirements is made easy by O2 with Filter and Partition.
Grouping (see e.g. tutorial Table Combinations) is also a kind of a selection mechanism. In that case the "selection" is done with help of an index column. With Filter and Partition general requirements can be formulated and applied to any type of table.
Filter
Executable: o2-analysistutorial-filters
Filters are used to select table entries only which comply with given reqirements.
SpawnExtendedTables
In task SpawnExtendedTables a couple of filters are defined. One of them (posZfilter) acts on collisions, the others on tracks. Note, that Filters is defined in namespace o2::framework::expressions.
struct SpawnExtendedTables {
.
.
Configurable<float> ptlow{"ptlow", 0.5f, ""};
Configurable<float> ptup{"ptup", 2.0f, ""};
Filter ptFilter_a = aod::track::pt > ptlow;
Filter ptFilter_b = aod::track::pt < ptup;
Configurable<float> etalow{"etalow", -1.0f, ""};
Configurable<float> etaup{"etaup", 1.0f, ""};
Filter etafilter = (aod::track::eta < etaup) && (aod::track::eta > etalow);
float philow = 1.0f;
float phiup = 2.0f;
Filter phifilter = (aod::etaphi::nphi < phiup) && (aod::etaphi::nphi > philow);
Configurable<float> vtxZ{"vtxZ", 10.f, ""};
Filter posZfilter = nabs(aod::collision::posZ) < vtxZ;
Filters defined in front of a process function can be applied to the arguments of the process function using the templated function soa::Filtered<T>. T is used to deduce which of the declared filters can be applied. If there are several matching filters then they are all logically anded.
// process only collisions and tracks which pass all defined filter criteria
void process(soa::Filtered<aod::Collisions>::iterator const& collision, soa::Filtered<soa::Join<aod::Tracks, aod::TPhi>> const& tracks)
{
LOGF(INFO, "Collision: %d [N = %d out of %d], -%.1f < %.3f < %.1f",
collision.globalIndex(), tracks.size(), tracks.tableSize(), vtxZ, collision.posZ(), vtxZ);
for (auto& track : tracks) {
LOGF(INFO, "id = %d; eta: %.3f < %.3f < %.3f; phi: %.3f < %.3f < %.3f; pt: %.3f < %.3f < %.3f",
track.collisionId(), etalow, track.eta(), etaup, philow, track.nphi(), phiup, ptlow, track.pt(), ptup);
}
}
Partition
Executable: o2-analysistutorial-partitions
PartitionOutside
Partitions are subsets of existing tables, subsets of rows which are consistent with set requirements. The Partition function is templated and takes the type of table to work on as template argument. Partitions defined in front are created automatically when the process function is executed and are available within the process function. Note how in this example Filter and Partition are combined.
struct PartitionOutside {
.
.
.
// all defined filters are applied
using myTracks = soa::Filtered<aod::Tracks>;
// definition of partitions
Partition<myTracks> leftPhi = aod::track::phiraw < philow;
Partition<myTracks> midPhi = aod::track::phiraw >= philow && aod::track::phiraw < phiup;
Partition<myTracks> rightPhi = aod::track::phiraw >= phiup;
// partitions are created and provided within the process function
void process(aod::Collision const& collision, myTracks const& tracks)
{
// all defined partitions are available
LOGF(INFO, "Collision: %d [N = %d] [left phis = %d] [mid phis = %d] [right phis = %d]",
collision.globalIndex(), tracks.size(), leftPhi.size(), midPhi.size(), rightPhi.size());
for (auto& track : leftPhi) {
LOGF(INFO, "id = %d, from collision: %d, collision: %d; eta: %.3f < %.3f < %.3f; phi: %.3f < %.3f; pt: %.3f < %.3f < %.3f",
track.collisionId(), track.collision().globalIndex(), collision.globalIndex(), (float)etalow, track.eta(), (float)etaup, track.phiraw(), (float)philow, (float)ptlow, track.pt(), (float)ptup);
}
for (auto& track : midPhi) {
LOGF(INFO, "id = %d, from collision: %d, collision: %d; eta: %.3f < %.3f < %.3f; phi: %.3f <= %.3f < %.3f; pt: %.3f < %.3f < %.3f",
track.collisionId(), track.collision().globalIndex(), collision.globalIndex(), (float)etalow, track.eta(), (float)etaup, (float)philow, track.phiraw(), (float)phiup, (float)ptlow, track.pt(), (float)ptup);
}
for (auto& track : rightPhi) {
LOGF(INFO, "id = %d, from collision: %d, collision: %d; eta: %.3f < %.3f < %.3f; phi: %.3f < %.3f; pt: %.3f < %.3f < %.3f",
track.collisionId(), track.collision().globalIndex(), collision.globalIndex(), (float)etalow, track.eta(), (float)etaup, (float)phiup, track.phiraw(), (float)ptlow, track.pt(), (float)ptup);
}
}
};
PartitionInside
Partitions can also be defined and used within the process function. in order to have the partition filled use the bindTable(T t) method where t is the table the partition shall be applied to.
struct PartitionInside {
void process(aod::Collisions const& collisions, aod::Tracks& tracks)
{
for (auto& c : collisions) {
// create the partition groupedTracks
Partition<aod::Tracks> groupedTracks = aod::track::collisionId == c.globalIndex();
groupedTracks.bindTable(tracks);
// loop over the partition groupedTracks
for (auto& t : groupedTracks) {
LOGF(INFO, "collision global index: %d grouped track collision id: %d", c.globalIndex(), t.collisionId());
}
}
}
};