A data-table tile beside a magnifying glass and calculator, representing checking records before calculating an average.
Software Development

Before you average a CSV: check missing values and duplicate rows with pandas

Your Python script has printed an average. The arithmetic can be correct even when the rows going into it answer the wrong question. A repeated record, an unfinished item or an unknown value can change what that number means.

A CSV is a text file that stores a table, usually with commas separating its columns. pandas is a Python library for working with tables. Its DataFrame is the table object you will use below. Before calculating a mean, the sum of values divided by their count, check what counts as one observation.

This exercise uses seven rows and a short script. You need basic Python and pandas installed; the official pandas installation guide explains the setup. Put the CSV and script in the same folder and run the code from that folder.

A useful next step after learning to analyse data

Eva J Patel’s 26 October 2026 freeCodeCamp tutorial on building a pandas data analyser introduces loading, inspecting and summarising a dataset. Here, the focus is narrower: deciding which rows support one average, then stating its coverage. This is a practice exercise inspired by recent coverage, not a new pandas feature.

Start with a small file you can inspect

Illustrative example: a developer wants the mean recorded duration of completed build runs in one export. A build run is one execution of a software build job. The export should have one row per run. Its specification says durations are rounded down to whole seconds, so zero is a valid recorded value.

The seven exported rows, arranged as a table
RunStateSeconds
R101complete30
R102complete0
R102complete0
R103complete(blank)
R104running25
R105completetbc
R106complete90
R102 appears twice in the shaded rows. (blank) makes the empty R103 cell visible here; the CSV cell itself is empty. A recorded 0 is a value, while tbc is text.

Save this as builds.csv. The export contains a repeated row, an unfinished run, one blank and the text tbc. Do not assume these all mean the same thing.

run_id,state,seconds
R101,complete,30
R102,complete,0
R102,complete,0
R103,complete,
R104,running,25
R105,complete,tbc
R106,complete,90

Check the records before calculating

1. Keep the original values and inspect repetition

Purpose: Find records that need an explanation before removing anything.

import pandas as pd

df = pd.read_csv('builds.csv', dtype='string', keep_default_na=False)
print(df[df.duplicated('run_id', keep=False)])

Save the Python snippets in order as check_builds.py. The import gives pandas the short name pd. Reading columns as strings preserves the supplied text. With keep_default_na=False and no custom missing-value list, the blank remains an empty string at this stage.

The duplicate check prints both R102 rows. pandas documents how duplicated works: specifying run_id checks that column, while keep=False marks every row in each repeated group.

A repeated identifier is a question, not a deletion instruction. In another export, it could represent separate events for one run. Inspect the fields and confirm what a row represents. In this example, the export owner confirms that the identical R102 record was accidentally included twice.

2. Separate the records and the unknown values

Purpose: Make selection and conversion decisions visible.

# Only after confirming R102 is a repeated export row.
selected = df.drop_duplicates().copy()
closed = selected[selected['state'] == 'complete'].copy()
raw = closed['seconds'].str.strip()
closed['seconds_number'] = pd.to_numeric(raw, errors='coerce')
print('Blank:', raw.eq('').sum())
print('Invalid:', (raw.ne('') & closed['seconds_number'].isna()).sum())

drop_duplicates() removes identical rows here, keeping one copy. It is appropriate only after the example’s confirmation. The original df is retained. The next line selects completed runs, excluding R104 because its elapsed time is not a completed duration.

The raw values are kept alongside the converted numeric column. to_numeric with errors=”coerce” converts unparseable entries to missing numeric values. It does not tell you why an entry is unusable or repair it.

The two printed counts are Blank: 1 and Invalid: 1. R103 has no supplied duration; R105 contains tbc. Both lack a usable number, but keeping their original text preserves that distinction. Neither becomes zero. This small example does not validate every possible duration or status; real exports need rules for unexpected values too.

3. Report the mean with its coverage

Purpose: Explain exactly which records support the result.

measured = closed['seconds_number'].dropna()
print('Mean:', measured.mean())
print('Measured:', len(measured), 'of', len(closed))

The output is Mean: 40.0 and Measured: 3 of 5. The three included durations are 30, 0 and 90 seconds: their sum is 120, divided by three. Five unique completed runs remain after removing the confirmed repeated row, but two have no usable duration.

Follow the rows into the calculation

  1. 7exported rowsThe starting file.
  2. 6unique runsOne confirmed repeat removed.
  3. 5completed runsThe running job is excluded.
  4. 3measured durationsTwo completed runs have unknown durations.
The five unique completed runs
RunSecondsIn the mean?
R10130Included
R1020Included
R103(blank)Unknown
R105tbcUnknown
R10690Included

(30 + 0 + 90) ÷ 3 = 40 seconds

Measured: 3 of 5 completed runs. R102’s zero is included once. R103 and R105 remain in the completed-run count, but cannot contribute a numeric duration.

The repeated R102 export row and the running R104 job have already been excluded. The two unknown durations remain unresolved; neither has been changed to zero.

The explicit dropna() makes the measured subset visible. The pandas mean documentation also states that missing values are excluded by default. Getting a number back does not establish that every relevant record was measured.

Write a sentence the data can support

A suitable handover is: “The mean recorded duration is 40 seconds across three of five unique completed runs in this export. One blank and one unparseable duration remain unresolved. One confirmed repeated export row and one running job were excluded.”

This is a description of the observed subset. It does not establish the average for all five completed runs, show a trend or explain why builds take that long. No time period is provided in this practice file, so do not attach a reporting window or compare it with another period.

If the raw converted column were averaged immediately, the result would be 29 seconds. That calculation includes the repeated zero and the running job while ignoring the unusable entries. The difference comes from the selection rules, not from a better averaging formula.

Keep a short calculation note

Try one change: replace R106’s 90 with a blank and rerun the script. Expect a mean of 15 seconds from two of five completed runs. The lower number reflects less information; it does not demonstrate faster builds. Before sharing an average from your own export, make the same connection between the number, the selected rows and what remains unknown.

Sources checked 27 October 2026. The build export, row decisions and calculation note are Coded Female teaching material. Code and stated outputs were checked with pandas 2.2.3; current official API documentation was also consulted. The exercise is not a complete data-validation process.

Leave a Reply

Your email address will not be published. Required fields are marked *