Why an Apple Health export adds up to too many steps
You added up a day's steps from your Health export and got far more than the Health app shows. The file is not wrong and neither is your sum: the same steps are in it more than once.
An iPhone and an Apple Watch both count your steps, and the export keeps every record from both. The Health app shows one source for each stretch of time; adding up the file counts every walk twice. To get close to Health's figure, total each source separately for each day and take the largest, not the sum. VITALS does that in your browser tab, without uploading the file.
What is in the file
The export is a folder, usually zipped as export.zip, with a large file called export.xml inside. Every measurement is a Record line with a type, a value, a start and end time, and a sourceName: the device or app that wrote it. A walk with a phone in your pocket and a watch on your wrist produces two sets of step records for the same minutes, one named after the phone and one after the watch. A running app, a pedometer app or a smart scale that also writes steps adds a third.
The Health app does not add those together. It keeps an order of sources, which you can see and change under Health, Browse, Activity, Steps, Data Sources & Access, and for each stretch of time it takes the steps from the highest source that has any. The merged figure it shows is worked out when it is shown. It is not written into the export.
The three things in the file that count twice
| What | Why it doubles | What to do |
|---|---|---|
| Steps, distance, flights, active energy | The phone and the watch both record the same movement | Total each source for each day and take the largest, or keep one source |
| Sleep | The phone writes time in bed from your bedtime; the watch writes the stages it detected, awake included | Count only the records whose value starts with HKCategoryValueSleepAnalysisAsleep |
| Blood pressure, meals | Each reading is written as two Records at the top level and again inside a Correlation that groups them | Skip Records inside a Correlation |
Sleep has a second catch. A night that starts at 11 pm is split across two calendar dates, so totalling sleep by the date each record starts gives short nights on some days and long ones on others. It reads better to give a night to the morning it ends.
Doing it in a spreadsheet
An export.xml of a few years is usually hundreds of megabytes and often several gigabytes. Excel stops at 1,048,576 rows, and a heart rate record every few minutes passes that within a year or two. Even when it fits, the XML has to be turned into rows first. If you already have the step records as rows with a date, a source and a value, the fix is a pivot table with date as rows and source as columns; the largest column in each row is the day's figure.
Doing it in Python
Most scripts online do df.groupby("day")["steps"].sum(), which is the double count. Grouping by day and source first, then taking the largest, is the fix:
import xml.etree.ElementTree as ET
import pandas as pd
rows = []
for _, el in ET.iterparse("export.xml"):
if el.tag == "Record":
if el.get("type") == "HKQuantityTypeIdentifierStepCount":
rows.append((el.get("startDate")[:10], el.get("sourceName"), float(el.get("value"))))
el.clear()
df = pd.DataFrame(rows, columns=["day", "source", "steps"])
by_source = df.groupby(["day", "source"])["steps"].sum()
daily = by_source.groupby(level="day").max()
iterparse reads the file as a stream, and el.clear() drops each record once it is read, so a large export does not have to fit in memory. startDate[:10] is the date in the time zone the record was made in, which is how Health dates it after a trip. On the test export VITALS is checked against, where the phone goes on most walks with the watch, the plain sum is 73% too high.
Why the largest source is a little under Health's figure
Taking the largest single source never counts a step twice, but it is not quite Health's merge. On a day the watch was charging for an hour, Health takes that hour from the phone and the watch for the rest. The largest source for the day is the watch alone, without that hour. Usually the difference is small; on a day with a long gap it can be a few thousand steps. Reproducing Health exactly would mean merging minute by minute in your own source order, and the export does not record the order.
Without writing any code
Drop the export.zip on VITALS. It reads the file in your browser, lists every kind of record with its sources, and shows each day's figure with the largest source by default, the sum of every source, or one source alone. Sleep is counted as time asleep with the night given to the morning, and the grouped blood pressure copies are left out. Any of it saves as CSV. It reads the zip as it goes, so a file of a gigabyte or more is fine, and nothing is uploaded: the export holds years of heart rate and sleep, plus the date of birth and sex set in Health.
Questions people ask about Why an Apple Health export adds up to too many steps
Why are the steps in my Apple Health export higher than the Health app?
Because the export keeps every source's records. An iPhone and an Apple Watch both count the same walk, so adding up every StepCount record counts it twice. Health shows one source for each stretch of time. Total each source per day and take the largest to get close to Health's figure.
How does the Health app choose between iPhone and Apple Watch data?
It keeps a priority order of sources, under Health, Browse, the data type, then Data Sources & Access, where it can be reordered. For each stretch of time it uses the highest source that has data. That merge is not written into the export.
How do I get daily steps from export.xml in Python?
Read the StepCount records with iterparse, group by day and source and sum, then take the largest source per day: df.groupby(["day", "source"])["steps"].sum().groupby(level="day").max(). A plain groupby on day alone double counts.
Why is my sleep total in the export too high?
The iPhone writes time in bed and the watch writes sleep stages, awake time included, for the same night. Count only records whose value starts with HKCategoryValueSleepAnalysisAsleep, and pick one source per night.
Why do blood pressure readings appear twice in export.xml?
Each reading is written as a systolic and a diastolic Record at the top level, and again inside a Correlation that pairs them. Skip the Records inside Correlation elements and each reading is counted once.
Can I open export.xml in Excel?
Usually not in full. A few years of data is often gigabytes and more than Excel's 1,048,576 rows. VITALS reads it in a browser tab and saves one kind of record, or one row per day, as a CSV that Excel opens.