This project estimates an electric car's signed battery current from signals recorded at the same moment: speed, acceleration, pedal position, road grade and the ECO / air-conditioning states. Positive current means the battery is being drained. Negative current means energy is flowing back through regenerative braking.
The aim is a model that works on new drivers and a new day, not one that memorises the route.
Built for Python for Engineers at Türk-Alman Üniversitesi (Winter 2026).
python -m venv .venv
.venv\Scripts\activate # Linux/macOS: source .venv/bin/activate
pip install -r requirements.txt
# put dataset.csv from Kaggle into data/raw/
python main.py --data data/raw/dataset.csv
pytest -qEvery figure and table is written to results/. The pipeline does not open a
window, so it also runs on a machine without a display.
To read the full story with outputs, open exploration.ipynb on GitHub. To run
it yourself:
pip install -r requirements-notebook.txt
jupyter lab exploration.ipynbNine trips by nine drivers over the same ~76 km route in Brazil, October 2023. The file has 24,277 rows and roughly 2 s sampling.
The raw CSV is not included here: it is a GPL-3.0 dataset and keeping it out
keeps the repository small. Download dataset.csv from the Kaggle link above and
place it in data/raw/.
If you use the data, cite the original paper:
dos-Reis, M., Frison, C. I., Teixeira, F. C., & Marques-Neto, H. T. (2024). Using Electric Vehicle Driver's Driving Mode for Trip Planning and Routing. Journal of Internet Services and Applications, 15(1), 410–423. https://doi.org/10.5753/jisa.2024.3805
- Cleans the data.
- Explores how the car is driven.
- Fits a simple physics model.
- Trains a Random Forest.
- Compares both with a mean baseline.
- Checks where the errors come from.
The file looks tidy but is not:
| Problem | What happens |
|---|---|
| Rows from all trips are mixed together | Sort by trip and time first |
| 97 timestamps are impossible or contradict the recording order | Drop those rows; they cannot be placed in time |
| Pack voltage is broken on 7 of 9 trips (0–25 V instead of ~350 V) | Keep the rows, mark them with vol_valid |
| 4 altitude values are 0 and 21 GPS fixes fall outside Brazil | Flag them; do not delete the rows |
The accelerometer AX has a per-trip offset |
Use dSPD/dt instead; AX is not a reliable acceleration signal |
After cleaning: 24,180 rows, 0.40 % removed. The full report is in
results/tables/data_quality_report.md.
The car's power balance at the battery is:
I = [ m·a·v + ½·ρ·C_d·A·v³ + m·g·sin(θ)·v + C_rr·m·g·v ] / (η·V_nom)
+ (P_base + P_air·AIR) / V_nom
vis speed andais acceleration fromdSPD/dt.θis road grade, estimated from altitude and distance over the last ~30 s.m,C_d,A,C_rrandρdescribe the car and the air.ηis drivetrain efficiency, assumed 0.90.V_nomis 350 V, because the measured voltage is unreliable.P_baseandP_airare the base and air-conditioning loads.
Dividing by voltage makes the equation linear in six coefficients. They are fitted with ordinary least squares on the training rows only.
Assumptions: steady motion over each sample, no wheel slip, one efficiency for driving and regeneration, and an auxiliary load that does not depend on speed. Speed, acceleration, pedals and current are also checked against physical ranges; no row failed those checks.
The fitted values are then translated back into real quantities: two independent
mass estimates agree within 10 % (873 kg and 959 kg), C_dA is 0.917 m² and
C_rr is 0.0095. The absolute mass is about 40 % below the published kerb mass;
the likely reasons are the coarse 2 s acceleration estimate and using one
efficiency for both driving and regeneration. That is reported as a limitation,
not hidden.
The rows are sorted by time. The first 80 % is the training set, the last 20 % is the test set. Nothing is shuffled, and the test set contains drivers and a day the model has not seen.
The split falls on 26 October 2023:
- Training: 19,344 rows.
- Test: 4,836 rows.
Everything learned — coefficients, missing-value imputer, Random Forest settings — is fitted on the training set only. The Random Forest is tuned with time-series cross-validation inside the training set.
A second check holds out the last 20 % of every trip, so all nine drivers appear in the test set. The ranking does not change.
| Model | Test MAE | RMSE | Bias | R² |
|---|---|---|---|---|
| Mean baseline | 40.35 A | 54.58 | −2.84 | −0.003 |
| Linear: speed + acceleration | 31.96 A | 43.94 | −2.62 | 0.350 |
| Physics model | 25.03 A | 36.05 | −0.47 | 0.562 |
| Random Forest | 5.43 A | 9.43 | −0.47 | 0.970 |
The Random Forest is much better, but there is a catch. The accelerator pedal alone correlates with current at r = +0.91, and 90 % of the forest's importance lands on the pedal. In practice it has learned this car's pedal-to-current map. That is a valid signal, but it will not transfer to another EV as well as the physics model would.
The physics model is the one more likely to generalise. The forest is the one that wins on this dataset.
- The mean baseline cannot follow demand: it is about 36 A too low above 80 km/h and 29 A too low uphill.
- The physics model is best while braking (14.6 A) and worst at high speed (up to 45 A), where aerodynamic assumptions matter most.
- The Random Forest stays between 2.6 and 7.4 A in every speed band. Its largest remaining bias is about −3.5 A at 80–100 km/h.
Trip 4 switches ECO mode on and off four times, so it looks like a clean test. It is not. ECO-on windows also have about twice the accelerator pedal (27 % vs 12 %) and sit on slightly uphill road (+2.2 % vs −1.2 % grade). Raw current is therefore higher with ECO on: 49.5 A vs 19.7 A.
Compare only samples in the same speed band and the same pedal range, and the answer flips: ECO-on current is 16–34 A lower, about −20 A on average. The mode does save energy; the raw comparison was really measuring the driver's right foot.
- Real data is rarely clean. The cleaning decisions are part of the result.
- A simple physics model is easier to explain and travels better than a model that only fits one car.
- A raw before/after comparison can point the wrong way. Matching speed and pedal turned a +30 A "increase" into a −20 A saving.
main.py command-line entry point
exploration.ipynb full analysis with saved outputs
src/ cleaning, features, models, plots, reports
tests/ 28 tests
results/figures/ fig01 … fig07
results/tables/ data quality and model results
data/raw/ dataset.csv (not committed)
- Random seed is fixed (
RANDOM_SEED = 42). - Paths are resolved from the repository root.
python main.py --data <path>runs everything.- Add
--stage clean,--stage exploreor--stage baselineto run one part. - Add
--no-tuneto skip the grid search for a quick run. pytest -qruns 28 tests; all pass.- The full pipeline takes about a minute on a laptop.
- Code: MIT, © 2026 Can Keçilioğlu.
- Dataset: GPL-3.0, © the original authors. The raw CSV is not redistributed.

