Data analyst reviewing pandas GroupBy results and missing-key rows on a computer at work.

Find the Rows Missing From Your pandas GroupBy Totals

Mon, Oct 5, 2026

An analyst groups data by region and channel but notices that a few rows seem to have vanished in the aggregation. A seemingly plausible grand total appears, but what if entire rows were silently dropped? The real question is: Does the output table represent every source row that should have been counted under the declared grouping rules? This article shows how to prove whether the grouped result covers the intended population or has omitted records. We demonstrate with a concrete 8-row synthetic dataset and pandas 2.2.3, identifying exactly which rows the default GroupBy drops when keys are missing. We then lay out a decision playbook: either accept the exclusion (with an approved policy), repair the aggregation logic, hold the incomplete records in quarantine, or recompute and reconcile published aggregates. Along the way, we keep careful evidence: raw source rows, ID membership, and a reconciliation equation of sums. The outcome is a reproducible acceptance and repair recipe for analysts to audit any pandas GroupBy in CPython 3.13.5.

Define which source rows the aggregate must represent

First, declare the dataset and grouping contract. Our source contains eight rows (R1–R8), each with a unique row_id, two grouping dimensions region and channel (nullable strings), and an amount (nullable Int64). There are no additional filters – the table itself is the raw population. We must specify upfront: every source row is identified by its row_id, and the report should include exactly those rows whose grouping keys match the declared policy. An attractive-looking table or reasonable grand sum is no proof of completeness; we need a formal membership check.

For example, imagine an unspoken rule that rows with missing region or channel should not appear in any output group. If this is the business contract (to exclude null keys), then the data owner must approve that exclusion. By contrast, if the policy is to include them explicitly (treating null as a valid category), or to set them aside, the analyst must detect missing-key rows before grouping. Refonte’s Python data-cleaning workflow emphasizes precisely this: “define the intended row grain and critical fields” and preserve the raw data before transformation. Thus we first inventory all source rows and their keys:

Row_ID

region

channel

amount

R1

North

web

10

R2

North

store

20

R3

None

web

100

R4

None

web

-100

R5

South

None

30

R6

South

store

None

R7

None

None

0

R8

East

store

40

(Rows R3–R5 and R7 have one or both grouping keys missing; R6 has missing amount.)

We’ll refer to “missing” keys as literal null (None) values, not as label strings. Note that a well-formed output table can still be “smiling” on totals while omitting some rows entirely. The analyst’s first task is to list every row and confirm the intended inclusion policy. A nice sum is not enough – we need proof of identity coverage. As a precaution, we ensure row_id is unique (any duplicates would break this reconciliation exercise) and that all rows R1–R8 are candidates unless explicitly filtered out by policy.

Pin pandas and the nullable input schema

Next, fix the software environment and schema exactly. We work in CPython 3.13.5 with pandas 2.2.3 (the reference environment). This ensures any reader can reproduce these results; if another version is used, one must rerun the fixture and compare. In code, we create the DataFrame and set precise dtypes:

import pandas as pd

ROWS = [
    ('R1', 'North', 'web', 10),
    ('R2', 'North', 'store', 20),
    ('R3', None, 'web', 100),
    ('R4', None, 'web', -100),
    ('R5', 'South', None, 30),
    ('R6', 'South', 'store', None),
    ('R7', None, None, 0),
    ('R8', 'East', 'store', 40),
]
df = pd.DataFrame(
    ROWS, columns=['row_id', 'region', 'channel', 'amount']
)
df = df.astype({
    'row_id': 'string',
    'region': 'string',
    'channel': 'string',
    'amount': 'Int64',
})

We use pandas’ new nullable string dtype for region and channel, and nullable Int64 for amount. This means any missing entry is exactly NaN (not an empty string or placeholder). In particular, a missing channel (None) is not a category label but a true null. We disallow implicit conversions or fills; we do not replace nulls with a sentinel. The grouping dimensions and measure are now distinct: a null grouping key affects whether a row joins any group, whereas a null amount affects only the numeric sum. We keep these decisions separate.

Importantly, we will call df.groupby(KEYS, dropna=..., observed=True, sort=False) explicitly. The observed=True flag is pinned even though our keys are string (not pandas Categorical). It only suppresses expansion of unseen categories, which doesn’t apply here. We use sort=False so that group keys appear in the order of first appearance (for reproducible output order). If the keys were Categorical, observed would control unseen categories, but since we used string dtype, its effect is only to silence potential warnings.

Give dimensions and measures different missingness policies

Note: a missing grouping key is not the same issue as a missing measure. If channel is null, it determines group membership (which group or none). If amount is null, it only affects the sum (essentially leaving that group’s total uncertain). We do not unify these policies. For example, row R5 has a valid region but a null channel: it should form a group with key (South,None) if we include nulls, but it contributes no numeric value. Meanwhile, R6 has a null amount but valid keys (South, store); it still belongs in that group, but provides no counted amount. We will report count of non-null amount separately from the row count, to make this distinction clear. In the evidence, we will denote null keys as None (no fabricated label like 'Unknown') to avoid confusion. (If your business convention is to use an empty string or a specific label for “missing”, that can be handled explicitly, but here we treat the literal null as the missing dimension.)

Write the group-membership oracle before running GroupBy

Before using pandas, we manually build a literal membership oracle. This is a table of every group key, the exact member row IDs, row count, non-missing-value count, and the nullable sum. Constructing it by hand (or a separate script) avoids circular reasoning: we do not simply run GroupBy twice and compare. Instead, we hard-code the expected result from our authoritative knowledge of the source:

region

channel

Row IDs

rows

measured

amount_sum

North

web

R1

1

1

10

North

store

R2

1

1

20

None

web

R3, R4

2

2

0

South

None

R5

1

1

30

South

store

R6

1

0

None

None

None

R7

1

1

0

East

store

R8

1

1

40

(Entries marked None in the key are actual nulls; amount_sum=None indicates unknown total because all values were null.)

This seven-group table is our “oracle” of membership. It shows exactly which row IDs belong to each (region, channel) pair. Crucially, it tells us that the (None, web) group contains R3 and R4 (with net sum 0), and (South, None) contains R5 (30), and (None,None) contains R7 (0). It also confirms R6 stands alone with no measured amount. Having this independent reference means any pandas output can be checked against it. We sorted the row IDs in each group for readability. This is stronger than a second pandas run because it does not rely on pandas logic at all.

We also verify that each row_id is accounted for exactly once across all groups: all R1 through R8 appear in one of the listed groups. Treating duplicate row IDs as a contract violation, we assert the input’s row_id column is unique. If duplicates did exist, we would flag that as an input schema error (unless there was a separate multiplicity contract). In this dataset, uniqueness holds by construction. In summary, this oracle guarantees a one-to-one mapping between source rows and output groups. Any discrepancy later will indicate dropped or misallocated rows.

Observe the default exclusion of missing keys

Now we run pandas GroupBy under its default behavior (which is dropna=True). We explicitly set dropna=True, observed=True, sort=False to avoid ambiguity. The code (conceptually) is: df.groupby(['region','channel'], dropna=True, observed=True, sort=False). The result includes only the groups with no missing key. In our data, that means it keeps rows R1, R2, R6, and R8; all other rows (R3, R4, R5, R7) have at least one null key and are dropped. The groups produced are:

1. (North, web) – contains R1, rows=1, measured=1, amount_sum=10.

2. (North, store) – contains R2, rows=1, measured=1, amount_sum=20.

3. (South, store) – contains R6, rows=1, measured=0, amount_sum=None (since the only value was null).

4. (East, store) – contains R8, rows=1, measured=1, amount_sum=40.

The combined sum of amount_sum over these groups is 70. This matches the sum of the non-missing amounts (10+20+40), but is only the partial total: the true source sum is 100. The omitted rows (R3, R4, R5, R7) contributed an additional 30 (in R5) to reach 100. The key point is that the default GroupBy output table is well-formed (all columns complete, sum=70 looks plausible) but has lost half the rows.

In code we would see, for instance:

base = df.groupby(
    ['region', 'channel'], dropna=True, observed=True, sort=False
).agg(
    row_ids=('row_id', tuple),
    rows=('row_id', 'size'),
    measured=('amount', 'count'),
)
base['amount_sum'] = base['amount'].sum(min_count=1)
print(base)
Result:
                  row_ids  rows  measured  amount_sum
region  channel
North   web       ('R1',)      1         1          10
        store     ('R2',)      1         1          20
South   store     ('R6',)      1         0        <NA>
East    store     ('R8',)      1         1          40

We see exactly the four groups listed above (with R6’s sum shown as <NA>). Only R1, R2, R6, R8 appear. In summary, the default behavior excludes any group whose key contains a null. Here that policy removed R3–R5 and R7 silently. The observed=True and sort=False flags were used but they affect ordering/categorical expansion, not null-key admission. We also note that R6 was included despite its missing amount: a missing measure does not cause row exclusion.

Keep equal totals from hiding omitted rows

Could an equal grand total fool us into thinking “no rows lost”? To show that totals alone are insufficient, consider only rows R1–R4. In this subset, the visible amounts (10 + 20) sum to 30, and the true total (10 + 20 + 100 - 100) is also 30. Yet the default grouping omits R3 and R4 entirely.

5. Default grouping with dropna=True on R1–R4 yields groups (North, web) and (North, store) summing to 30 (only R1 and R2).

6. Full grouping with dropna=False yields (North, web), (North, store), and (None, web). The (None, web) group contains R3 and R4 with net sum 0 (100 + -100). The overall total is still 30, but two rows (R3,R4) were missing in the first output.

Thus both outputs report a total of 30, but the default output’s identity set is {R1, R2} versus the true {R1, R2, R3, R4}. This is the critical counterexample: two omitted negative and positive amounts can cancel out, leaving the headline total unchanged. Because we are using integers, there is no floating-point magic here – the sums match exactly, even though the membership differs. We highlight that this is entirely non-time-based; unlike time resampling boundaries (see Refonte’s discussion “time-bin membership is a different grouping problem”), this is about composite keys. A difference of exactly zero at the total does not guarantee no data was lost.

In practice, R3 and R4 had equal and opposite values. If instead their sum happened to be nonzero, the discrepancy would show up in the grand total and trigger suspicion. But one cannot assume cancellation will never happen. The safe approach is never to rely on totals alone. We must compare row-level membership explicitly.

This scenario is distinct from time-based grouping issues like resample bins; there the question is about interval inclusion, whereas here the omitted group (None, web) is a separate categorical bucket.

Retain missing-key groups when the contract allows them

If the business contract is to include rows with missing keys as their own categories, we must use dropna=False. In pandas 2.2, setting dropna=False instructs groupby to treat nulls as a legitimate key value. Running the same grouping with dropna=False yields all groups in our oracle:

7. (North, web): R1 ⇒ sum=10

8. (North, store): R2 ⇒ sum=20

9. (None, web): R3, R4 ⇒ sum=0 (R3+R4)

10.          (South, None): R5 ⇒ sum=30

11.          (South, store): R6 ⇒ sum=None

12.          (None, None): R7 ⇒ sum=0

13.          (East, store): R8 ⇒ sum=40

Every source ID R1–R8 appears exactly once in this result. The total of amount_sum is 100, matching the source total. In this mode we explicitly retain missing-key groups, effectively making “null” a reporting category. The canonical output now matches the expected oracle table above. No row is lost. We emphasize that we did not invent a customer name or channel label for the None values – the dimensions remain null. If a real dataset used an empty string '' or a literal 'Unknown' to denote missing, pandas would not treat those as null; they would form normal groups. We consider such negative controls separately (below). For now, dropna=False gives a complete row reconciliation:

kept = df.groupby(
    ['region', 'channel'], dropna=False, observed=True, sort=False
).agg(
    row_ids=('row_id', tuple),
    rows=('row_id', 'size'),
    measured=('amount', 'count'),
)
kept['amount_sum'] = kept['amount'].sum(min_count=1)
assert set(r for ids in kept['row_ids'] for r in ids) == set(
    f'R{i}' for i in range(1, 9)
)

All 8 IDs are admitted, and the output exactly matches our oracle (row counts and sums, including R6’s unknown sum). The contract of “include null categories” has been satisfied. The key point is that we did not fill region or channel in the input DataFrame; we only normalized the representation in our evidence.

Preserve the difference between missing and a literal label

As a check, consider treating missing keys explicitly. For example, if R3’s region were set to '' (empty string) or 'Unknown' instead of None, groupby would form a group ('', 'web') or ('Unknown','web'). This is a different policy: missingness-by-encoding rather than true null. In our setup, we do not alter the raw data – we do not guess labels. We mention this only as a conceptual control. If we did run such a variant, it would belong in a broader data normalization step. It is not part of the executed analysis above, but it highlights that pandas treats actual null differently from any string. (Hence any use of literal placeholders must be an explicit step, not an automatic pandas feature.)

Quarantine incomplete keys without losing their records

If we neither accept dropping nor wish to mix nulls into the main table, a third option is quarantine: exclude missing-key rows from the published aggregation but keep them in a separate log or table for review. For example, we define incomplete-key rows as those where region or channel is null. In our dataset, that is R3, R4, R5, and R7. We can pull them out:

incomplete = df[df[['region', 'channel']].isna().any(axis=1)].copy()
incomplete['reason'] = 'missing grouping key'
accepted = df[~df.index.isin(incomplete.index)]

The quarantined subset is:

row_id

region

channel

amount

reason

R3

None

web

100

missing grouping key

R4

None

web

-100

missing grouping key

R5

South

None

30

missing grouping key

R7

None

None

0

missing grouping key

(Note: we preserve original row_id and values.) The accepted subset (to be grouped) would then be R1, R2, R6, R8. We must verify the partitions: the accepted IDs {R1,R2,R6,R8} and quarantined IDs {R3,R4,R5,R7} are disjoint, and their union is the full set {R1,…,R8}. Formally:

14.          No overlap: {R1,R2,R6,R8} ∩ {R3,R4,R5,R7} = ∅.

15.          Complete coverage: {R1,R2,R6,R8} ∪ {R3,R4,R5,R7} = {R1,…,R8}.

These equations ensure we have accounted for every source row exactly once. In terms of measures, we similarly reconcile: the sum of accepted amounts (10+20+0+40 = 70) plus the sum of quarantined amounts (100 + -100 + 30 + 0 = 30) equals the original 100. We can even write this as a reconciliation formula:

16.          Identities: Accepted_IDs ∪ Quarantined_IDs = All_IDs.

17.          Values: 70 (accepted sum) + 30 (quarantined sum) = 100 (source total).

Maintaining such a quarantine table is akin to preserving evidence of “unknown” cases. It allows a reviewer to see all originally excluded rows with their reasons. If the grouping keys are considered unreliable for reporting, we are at least not losing the data – we are flagging and isolating it.

Separate group size, measured count and nullable sum

In each group, we distinguish three statistics: (1) group size (size or 'rows'), (2) count of non-null measures (count), and (3) sum with min_count=1. These capture different aspects of the data. For instance, group (South, store) has one row (size=1), but that row’s amount was missing, so the “measured” count is 0. According to pandas’ docs, size always counts total rows, while count excludes null values. We explicitly used these: 'rows'=size and 'measured'=count. The sum with min_count=1 yields None when all values are null, as in (South,store) where R6 is alone with no numeric value. In contrast, group (None, web) has size=2, measured=2 (both R3 and R4 have non-null amounts), and sum=0 (100 + -100). Group (None,None) has size=1, measured=1, sum=0 (R7’s zero).

Thus, in the (South, store) group we see rows=1, measured=0, amount_sum=None. In (None, web): rows=2, measured=2, amount_sum=0. And in (None,None): rows=1, measured=1, amount_sum=0. We emphasize: an unknown sum (None) is not the same as a zero sum. The former indicates no data to sum; the latter is a real sum of existing zeros or offsetting values. The min_count=1 parameter makes this explicit: with fewer than 1 non-NA value, result is NA. Analysts and dashboards must interpret <NA> as “missing data” (unknown economic value), not as zero revenue.

Keep an unknown amount distinct from measured zero

It’s instructive to contrast R6’s group with others. The (South, store) group containing R6 has no measured values, hence sum=None. By contrast, the (None, web) group has two measured values that cancel to zero. And the (None,None) group has one measured zero. In summary:

18.          Unknown sum (None) occurs only when all contributions are NA (as with R6).

19.          Measured zero occurs when one or more contributions sum to 0 (as with R3+R4=0, or R7’s 0).

These are fundamentally different. For data quality, we must retain the <NA> flag. A downstream report must not treat that <NA> as 0.00 or drop the group; it’s a sign of missing knowledge.

Test reordering and declared boundary cases

Our reference harness also shuffles the input to confirm determinism. A random shuffle of the rows (with a fixed random state) must yield the same canonical group membership. In fact, a check canonical(grouped(df.sample(frac=1, random_state=7), True)) == EXPECTED passes, confirming groupby’s results are order-independent here (no dependence on DataFrame order). We note, however, that in general not all pandas operations are fully order-agnostic, but for this aggregation they are.

Beyond order, we should consider boundary scenarios (some untested cases to add in regression tests):

20.          Duplicate row IDs: If the input had duplicate row_id, our contract of unique identity is violated. We would flag this as an error (and halt) unless a multiplicity contract is explicitly provided. E.g., if R1 appeared twice, we could sum its values into one ID, but typically that is not allowed without a rule. We do not silently merge or ignore duplicates; we require unique keys.

21.          All-missing-key input: If every row had at least one null key, then with dropna=True the groupby would produce an empty table (no groups). With dropna=False, all rows would collapse into one group keyed by (None,None) or multiple (None,X). It’s important to define what to do: likely quarantine all or adopt the keep-null policy. We would treat an empty result (in dropna=True) as a red flag requiring either a defined “null bucket” or quarantine.

22.          Empty DataFrame: If the input has zero rows, then grouping simply yields an empty output. Our code should handle this gracefully (perhaps returning an empty table with the right columns). We should ensure tests cover df.iloc[0:0].

23.          Unexpected new key values: If suddenly a new region or channel value appears that wasn’t in the contract (say a new region name), pandas will simply create a new group. This is usually fine, but it could violate a data dictionary. We would detect that by comparing actual groups to expected categories. Again, this is a contract issue: either allow dynamic categories or fail.

These additional cases should be tested in regression: e.g., grouped(df[special_case], dropna=...) and verifying the outcome or appropriate error. They are not observed in our 8-row fixture run but should be documented and checked whenever the logic changes.

Capture a reproducible aggregation evidence bundle

For any reported result, we compile an evidence bundle that fully describes the aggregation step:

24.          Input signature: the raw DataFrame (row IDs, fields, dtypes) before grouping.

25.          Declared filters/keys: exactly which columns were grouped on, and whether dropna was True/False, observed, etc.

26.          Runtime versions: Python and pandas versions (3.13.5 and 2.2.3 here), to ensure reproducibility.

27.          Identity partition: explicit lists of accepted vs excluded (quarantined) IDs, as shown above.

28.          Output tables: the groupby result table(s), including columns of row IDs, counts, etc. (We keep the pre- and post-aggregation tables.)

29.          Checks/Assertions: results of consistency checks, e.g. that union of ID sets covers all rows.

We can express the reconciliation with simple equations. For example, for our default-case run:

These equations show that the four excluded rows sum to 30, which, plus 70, matches the source total. These equations, along with the bundle, document exactly what happened. Importantly, they clarify that a partial (policy-correct) report is not the same as a full-population report – if any rows are quarantined, the published metrics are strictly partial. We record the partition of identities and the partial sums as two separate components of the audit record. This way, reviewers (or automated tests) can verify that every raw row is either in the published output or explicitly accounted for in quarantine.

Recompute affected outputs from retained source rows

Once we detect missing rows, the next step is correction. We identify where the error boundary occurred (the last saved aggregation result, perhaps in a notebook or intermediate report) and re-run from retained source rows. In our case, we locate where the group-by was materialized (e.g. a cell like sales_by_region = df.groupby([...]).sum()) and rerun it with dropna=False or with a split/union of accepted+quarantined. Then we compare group identities between the old and new outputs. For instance, comparing the 2-group output of the R1–R4 case to the 3-group oracle reveals the missing (None, web) group. We can highlight rows present in the new output but missing in the old – those are the losses.

It is important to list affected reports or dashboards. For example, a regional sales report would be incomplete because it lacks the “Unknown region” group (R3+R4). We mark it affected and plan to update it. A dashboard that only showed the total 30 might have been misleading; we cannot “recover” that missing group just by adjusting the label or number, because the information was not computed. Conversely, any downstream report that only uses the (North, web) or (North, store) numbers is unaffected. We categorize consumers as “known affected” if they rely on the full group-level breakdown, “unaffected” if they only see totals that are already correct, or “unknown” if we are not sure.

The key lesson: one cannot reconstruct dropped dimensions from grand totals. Changing a display label (“rename None to Unknown”) would not re-add R3/R4. The lost entries must be re-aggregated from the source. Hence, after rebuilding, we should propagate the fix through each consumer dataset or report that used the old aggregate. We explicitly preserve the old output as evidence (in case a rollback is needed) and produce a new output.

Choose accept, repair, hold or recompute

Finally, we must decide the action path. The choice depends on the approved missing-key policy and whether we trust current outputs. We summarize in a decision matrix:

Decision

Row identities included

Sum/measure knowledge

Policy action

ACCEPT

Only rows with complete keys (per contract)

Totals match declared subset (partial)

Use dropna=True; require analytics owner sign-off on excluding missing-key rows.

REPAIR

Include missing-key rows as explicit groups

Distinguish NA sums vs zeros

Use dropna=False; include null-key groups, flag missing sums. Owner approves adding categories.

HOLD

Split: published covers complete-keys; quarantined contains others

Published shows partial sums; full sums flagged

Keep dropna=True for main output; output a quarantine table as evidence.

RECOMPUTE

All source rows in output (full population)

New totals may change; include missing measures properly

Rerun grouping (often with dropna=False or similar fix) to fully reconcile.

30.          ACCEPT is chosen only if the business leader (analytics owner) decrees that missing-key rows are not part of the target population. In that case, we accept the smaller population and consider the published table final. We still note the exclusion in data governance logs.

31.          REPAIR (often the preferred policy) means we change the code/flag to dropna=False so that all groups appear. The analytics owner must authorize that policy change (it’s a business semantics decision). In practice, we would retest and republish the corrected table.

32.          HOLD is a middle-ground: we do not modify the published report, but we add a companion output (the quarantine table above) and annotate it as “for review”. This at least prevents ignoring the missing data, while keeping the original table stable. It’s a way to say “policy is under discussion.”

33.          RECOMPUTE is taken if the published numbers themselves were flawed (e.g. totals disagree with contract). We then recompute (with the chosen fix) and update all downstream assets. Recomputing might be required even if ACCEPT was chosen, if those consumers now realize they needed the missing group.

Each action must be authorized by the proper owner: the analytics/product owner who declared the report’s population. The code alone does not decide business meaning. The owner’s sign-off is what legalizes “Okay, exclude null regions” versus “we must include them.”

Effective analytics governance relies on clear metric definitions and lineage. As Refonte’s analytics article advises, maintain “a governed metric catalog with owners and test coverage” to avoid confusion. Here, that means documenting who approves dropping keys and how sums are computed.

Name the owner who authorizes the reporting population

(In practice, one should append to the decision table or report a note like: “Missing-region rows were excluded per policy approved by the Regional Sales Director.”) The person or team responsible for defining the scope of the report (sometimes a business analyst or product owner) must explicitly endorse the chosen population. For ACCEPT or REPAIR paths, this person is the ultimate authority. In our schema, they would confirm whether “null region” represents a real category or not. The code, by itself, is not the decider of business rules. We therefore include in our evidence the name/title of the owner (hypothetically) and the date/time they signed off.

Promote the repaired aggregation with regression evidence

When a fix is agreed, it must be released carefully. We update the grouping code or parameters (e.g. dropna=False) and retest the fixture to lock in new expected outputs. We version the policy (for example, “GroupingPolicy v2.0”) and run our regression tests on the canonical 8-row DataFrame to confirm they still produce the oracle table. We archive the before and after tables. Any visualization or notebook output should be annotated with the policy version used. In particular, dashboards built on the old numbers should be tagged “out-of-date” until reprocessed.

If anything goes wrong, we want a rollback plan: retaining the raw evidence (inputs and old outputs) ensures we can revert code without losing auditability. We never delete the source evidence; we treat it as immutable. Note that simply running one fixed notebook does not automatically fix all reports. Each downstream system needs to be re-aggregated with the new logic, which may involve data pipelines or separate queries. We log all known consumers as “to be updated”. There may be unknown or ad-hoc consumers we can’t discover; that uncertainty is part of the risk, and we document it as such (for example, noting “Other notebooks not yet audited”).

Turn aggregation evidence into data-science practice

This careful handling of grouping and missing data is a concrete example of robust analytics engineering – akin to treating data preparation as code, with tests and versioning. It underscores why auditability and reproducibility are essential skills in any data scientist’s toolkit. Building your portfolio with such rigorous examples is exactly what practitioners need. As one Refonte guide notes, future employers expect “two or three deep projects with writeups” demonstrating production evidence.

If this material interests you, consider reviewing Refonte Learning’s Data Science Program. It’s a structured 3-month (12–14 hours/week) course that teaches Python, pandas, data cleaning, and analytics, just like in this lab. (Prerequisite: enrollment in a bachelor’s or postgraduate program.) The program emphasizes hands-on projects, version-controlled workflows, and analytical testing – aligning with the skills used above. In particular, our example of auditing a pandas GroupBy reflects the kind of portfolio-worthy project you might build during training. By connecting data ingestion, cleaning, and assurance steps, you produce “production evidence” rather than isolated notebooks.

In summary, every published aggregate should have explicit membership proofs and reconciliation steps. By accepting, repairing, holding, or recomputing with clear ownership and evidence, analysts ensure that no source row falls through the cracks. Treating data aggregation with the same rigor as software turns raw data cleaning into data-science expertise.