Suppose one batch succeeds once in two attempts and another succeeds 90 times in 100 attempts. Their individual rates are 50% and 90%. Averaging those percentages gives 70%, but the pooled attempt-level rate is 91 successes in 102 attempts, approximately 89.22%.
The distinction comes from the question being answered. Equal weighting describes an average over groups. Pooling counts describes the success rate over all observed attempts. Preserve the underlying counts so the intended calculation remains possible instead of storing only rounded percentages.
Prerequisites
Use Python 3.12 or a newer supported Python 3 release. This program requires only the standard library. Save it as weighted.py. It uses Python Fraction to retain an exact ratio until presentation.
from fractions import Fraction
def combined_rate(groups):
numerator = denominator = 0
for successes, total in groups:
if type(successes) is not int or type(total) is not int:
raise ValueError('integer counts required')
if total < 0 or not 0 <= successes <= total:
raise ValueError('invalid counts')
numerator += successes
denominator += total
if denominator == 0:
raise ValueError('no observations')
return Fraction(numerator, denominator)
rate = combined_rate([(1, 2), (90, 100), (0, 0)])
assert rate == Fraction(91, 102)
assert combined_rate([(0, 5)]) == 0
assert combined_rate([(5, 5)]) == 1
for bad in ([], [(0, 0)], [(3, 2)], [(True, 2)], [(-1, 4)]):
try:
combined_rate(bad)
except ValueError:
pass
else:
raise AssertionError('invalid counts accepted')
print('pooled fraction:', rate)
print(f'pooled percent: {float(rate) * 100:.2f}%')
print('unweighted group percent: 70.00%')
Run and expected output
python3 weighted.py
pooled fraction: 91/102
pooled percent: 89.22%
unweighted group percent: 70.00%
Read the calculation
The function adds successes and attempts separately, then constructs one fraction. It does not calculate and round a percentage for each row. The zero-attempt fixture contributes no weight and therefore changes neither total. If every group has zero attempts, the function raises an error rather than displaying a misleading 0%.
Counts must be ordinary integers. The explicit type checks reject booleans even though Python permits them in many integer operations. Negative attempts, negative successes, and successes greater than attempts violate the chosen count model and fail before aggregation.
Choose a useful reporting contract
For a dashboard, return the pooled rate together with total successes and attempts. A percentage without its denominator hides the difference between a tiny batch and a large one. Preserve per-group results too when group-level behavior matters; the pooled result can conceal a badly performing small group.
Before combining groups, confirm that they measure the same outcome over compatible populations and time windows. Repeatedly counting the same attempts in overlapping groups changes the meaning of the total. This small function cannot detect overlap because its input contains aggregate counts rather than observation identifiers.
If your business question intentionally gives each customer or experiment equal influence, an average of group rates may be appropriate. Name that statistic explicitly. Do not substitute pooled counts merely because the numerical result looks better.
Edge cases and troubleshooting
The exact fraction is converted to float only for a two-decimal display. Keep the exact value or original counts for later calculations rather than feeding the displayed percentage back into analysis. This tutorial does not calculate uncertainty intervals, significance tests, or causal effects.
If results disagree with an existing report, first compare the denominators and weighting rule. Then check for missing groups, overlapping windows, and earlier rounding. The printed 70% is the hand-calculated contrast for the two nonempty fixture groups; it is not the pooled result.
Explore more data-science tutorials and Python tutorials.