Skip to content

perf(deps): drop pandas and numpy from the default install - #1343

Open
davidberenstein1957 wants to merge 5 commits into
fix/powermetrics-nan-totalsfrom
scaling/03-dependency-extras
Open

perf(deps): drop pandas and numpy from the default install#1343
davidberenstein1957 wants to merge 5 commits into
fix/powermetrics-nan-totalsfrom
scaling/03-dependency-extras

Conversation

@davidberenstein1957

@davidberenstein1957 davidberenstein1957 commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Part of #1338.

Base branch: this is stacked on fix/powermetrics-nan-totals (#1345), not master. It touches the same ApplePowermetrics.get_details() block, and an earlier revision of this branch reinstated the very NaN bug #1345 fixes (its _mean() returned float("nan") for an empty sequence "to match the previous np.mean"). It is now rebased on #1345 and keeps that branch's 0 W behaviour, using statistics.fmean / math.fsum instead of numpy; the _mean() helper is gone. Retarget to master once #1345 merges.

pandas was used shallowly — CSV reading, a handful of row filters, one CSV writer — and pulled numpy in behind it. Both are now gone from the default install, replaced with csv, statistics and math. Nothing a user writes changes.

Measured on a clean 3.12 venv (uv venv && uv pip install .)

master branch
installed size 130 MB 68 MB
packages 39 37
cold import codecarbon 78.4 ms 37.8 ms

Import time is the median of 7 python -X importtime runs (before: 73.5/74.5/75.8/78.4/79.2/80.8/111.4; after: 36.0/37.6/37.8/37.8/39.2/40.5/1258 — the outlier is the fresh venv's cold page cache).

What actually loaded, traced by dumping sys.modules in a subprocess after a fresh import and after a default tracker run:

  • bare import codecarbon on master pulled numpy, 20.2 ms of the 78 — via import numpy as np at the top of core/powermetrics.py. numpy was never a declared dependency; it arrived transitively through pandas.
  • a default CSV tracker pulled pandas, numpy, prometheus_client, pycountry. prometheus_client was imported before the if OutputMethod.PROMETHEUS check; that import is now inside the branch.
  • typer, rich, questionary, click, authlib, joserfc, cryptography were never imported on either path.

What this deliberately does not do

The CLI/auth extras split from the original proposal is not here. It is worth 23 MB and 8 packages with zero runtime benefit — those imports are already off the hot path — and it breaks pip install codecarbon && codecarbon monitor, a documented headline feature. Its own mitigation is a two-release deprecation whose first half is a warning on every CLI invocation for no measurable gain. That belongs with the v4.0.0 removals, if ever.

pycountry (21 MB, the next biggest) also stays: it sits on the live geolocation path, and a lazy import with an empty fallback would be a silent country_iso_code/country_name regression. The bundled data cannot supply the alpha-2 → alpha-3 mapping.

Correction to the original proposal's inventory

It listed six shallow call sites and "no vectorised arithmetic", and missed output_methods/file.py entirely — the writer for the user's emissions.csv, which does dtype-preserving in-place row updates by run_id and all-NA column dropping. That is the real risk surface, not the reference-data parsing, and anyone costing this item off that inventory would have underestimated it. It is now on csv.DictWriter.

Tests

uv run pytest tests/ -q --ignore=tests/test_viz_data.py630 passed, 21 skipped (on top of #1345). test_viz_data.py fails to collect on master identically — dash not installed. uv run pre-commit run --all-files passes.

Two tests failed mid-way on float precision only. A side-by-side script ran the old pandas path and the new one over mock_intel_power_gadget_data.csv: all 22 columns agree to within 4.4e-16. Those two assertions were hardcoding pandas'/numpy's summation order in the last ulp, so they are now tolerance-based rather than having their expected numbers edited.

New guard in tests/test_package_integrity.py: runs a full offline tracker in a subprocess and asserts pandas, numpy and prometheus_client are absent from sys.modules. End-to-end in the bare venv, two update-mode runs produce the same 38-column, 2-row emissions.csv as master.

Compatibility

pip install codecarbon installs less and behaves the same; codecarbon[carbonboard] and [viz-legacy] still get pandas; a new all extra pulls everything; pandas moved to the dev group, since ten test modules read output CSVs with it.

⚠️ Public API break — needs a release note

DataSource.get_cloud_emissions_data() and DataSource.get_cpu_power_data() now return list[dict] instead of a pandas.DataFrame. Nothing in-tree breaks (three test modules and codecarbon/viz/, all updated — viz builds its own frame), but these are importable, undecorated, documented-by-name methods, so any downstream code calling .query(), .iloc, [col] etc. on the result breaks at runtime.

This repo has no CHANGELOG file — release notes are assembled by .github/release-drafter.yml from PR titles and labels — so there is nowhere to land this except here. Please label this PR breaking (or equivalent) so the drafter surfaces it, and consider holding it for the next minor at least.

Behaviour changes, both fixes

  1. Emissions.get_cloud_geo_region returned pandas' NaN for 30 of 40 cloud regions that have a city but no state, because if state is not None is true for NaN. It now returns the city, as the code plainly intended.
  2. Float means/sums are exactly rounded (fmean/fsum) instead of matching numpy's pairwise order — differences at 1e-16.

Risks

CSV float formatting now comes from str(float) rather than pandas' to_csv. These agree on repr-shortest for normal values, but not on NaN: str(float("nan")) is the literal "nan" where to_csv wrote an empty cell. That was a real output regression in an earlier revision of this branch and is now fixed — _as_csv_row renders any non-finite float as "", covered by tests/output_methods/test_file.py::test_non_finite_values_are_written_as_empty_cells (verified to fail when the guard is removed).

Blanking infinities is a deliberate divergence from pandas: to_csv wrote the literal inf, this writes an empty cell. An infinite energy or emissions figure is not a real measurement, so writing inf into emissions.csv propagates a garbage number into everything that reads the file, whereas an empty cell reads back as missing — which is what it actually is.

append mode previously called dropna(axis=1, how="all") on the single new row, writing a short, column-misaligned row whenever a field was None; this writes an empty cell instead — correct, but not byte-identical to that old broken output. Three malformed TDP values in cpu_power.csv (27.29.5, 33.34.8, 29.32.9) still raise on float() exactly as before; left alone, with a comment on why the column stays text.

extrasaction="ignore" on the DictWriter is defensive only and now carries a comment saying so: out() backs the file up and rewrites it from scratch whenever has_valid_headers() reports a mismatch, so a row can never carry a key outside fieldnames.

Not included: the CI bare-install job (that is #1342) and the docs/README install-instruction sweep.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.84536% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.44%. Comparing base (dd71abc) to head (afdf4cc).

Files with missing lines Patch % Lines
codecarbon/core/cpu.py 86.36% 3 Missing ⚠️
codecarbon/emissions_tracker.py 0.00% 1 Missing ⚠️
codecarbon/output_methods/file.py 96.55% 1 Missing ⚠️
Additional details and impacted files
@@                     Coverage Diff                      @@
##           fix/powermetrics-nan-totals    #1343   +/-   ##
============================================================
  Coverage                        91.43%   91.44%           
============================================================
  Files                               49       49           
  Lines                             5058     5094   +36     
============================================================
+ Hits                              4625     4658   +33     
- Misses                             433      436    +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

davidberenstein1957 and others added 2 commits August 12, 2026 20:38
The measurement core only ever used pandas for `read_csv` plus equality
filters and one mean, and numpy for four `mean`/`sum` calls. Between them
they account for 61 MB of a 130 MB default install and 26% of cold import
time, for work the stdlib `csv`, `statistics` and `math` modules do.

- parse the bundled reference CSVs with `csv.DictReader` (utf-8-sig, since
  impact.csv carries a BOM), coercing `impact`/`offsetRatio` to float at the
  boundary and mapping empty fields to None
- add `DataSource.find_cloud_region()` so the provider/region filter is
  written once instead of six times
- rewrite `FileOutput` and `IntelPowerGadget.get_cpu_details` on stdlib csv
- replace numpy in `powermetrics` with `statistics.fmean` / `math.fsum`
- import `prometheus_client` only when Prometheus output is requested
- move pandas to the `carbonboard` extra and add an `all` meta-extra;
  `codecarbon/viz/` builds its DataFrame from the returned rows

Default install: 130 MB -> 68 MB, 39 -> 37 packages, cold `import codecarbon`
78.4 ms -> 37.8 ms (median of 7).

Behaviour changes, both fixes: `get_cloud_geo_region` returned pandas' NaN
for the 30 of 40 cloud regions that have a city but no state, and now
returns the city; float means are now exactly rounded rather than matching
one library's summation order.

`DataSource.get_cloud_emissions_data()` and `get_cpu_power_data()` return
`list[dict]` instead of `DataFrame` — undocumented internal surface, but
worth a release note.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tput

Also document why extrasaction="ignore" is unreachable.
@davidberenstein1957
davidberenstein1957 force-pushed the scaling/03-dependency-extras branch from a7da11d to 7b58130 Compare August 12, 2026 19:08
@davidberenstein1957
davidberenstein1957 changed the base branch from master to fix/powermetrics-nan-totals August 12, 2026 19:08
davidberenstein1957 and others added 3 commits August 12, 2026 23:10
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…header

The viz extras install pandas but not numpy, which `codecarbon/viz/components.py`
imports directly; numpy used to arrive transitively through pandas in the default
install, so `pip install codecarbon[viz-legacy]` now breaks on import.

The CSV append path passed the row dict's key order as DictWriter fieldnames,
while `has_valid_headers()` accepts any permutation of the columns already on
disk. Read the header row from the file instead, so a reordered file gets
correctly aligned rows; an empty/headerless file is rewritten with a header.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s installed

Dropping pandas from the default install turned
DataSource.get_cloud_emissions_data() and get_cpu_power_data() into
list[dict] returns, which would break any external caller relying on the
DataFrame they used to get.

Internal callers now use new get_cloud_emissions_rows() /
get_cpu_power_rows() helpers, so the default path stays pandas-free. The
two public methods wrap those and lazily import pandas: a DataFrame when
pandas is present, the rows otherwise (a user without pandas could not
have been using the DataFrame anyway).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@davidberenstein1957
davidberenstein1957 marked this pull request as ready for review August 13, 2026 05:23
@davidberenstein1957
davidberenstein1957 requested a review from a team as a code owner August 13, 2026 05:23
@davidberenstein1957
davidberenstein1957 requested review from inimaz and removed request for a team August 13, 2026 05:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant