Skip to content

Repository files navigation

Data Science Work Samples

Sepideh Zamani — Utrecht, Netherlands

Three analyses on small tabular business datasets. Each reaches a negative or corrected result, and each is written that way deliberately.

A portfolio notebook reporting 85% accuracy on a clean dataset shows that its author can call .fit(). These show something I think matters more in practice: noticing when a result is wrong, tracing it back to its cause in the data, and reporting what the evidence actually supports.


1. Churn prediction — and why this dataset cannot support it

01_churn_prediction/notebooks/churn_analysis.ipynb

Built a churn classifier on 300 telecom records. Logistic regression reached ROC-AUC 0.559 against a 0.500 baseline — an apparent improvement that turned out not to be real.

Method: cross-validated pipeline (ColumnTransformer + StratifiedKFold, preprocessing fitted inside each fold), majority-class baseline, ROC curves on out-of-fold predictions, and a 200-run permutation test.

Finding: the score falls inside the distribution the same pipeline produces on randomly shuffled targets (p = 0.129). A data-quality audit explains why:

  • Service dependencies guaranteed by the schema are violated on nearly every row, in both directions — customers with no phone service carrying multi-line details, customers with no internet carrying active internet add-ons.
  • TotalCharges equals MonthlyCharges × tenure exactly on all 300 rows, so it carries no independent information.
  • 54% of customers are flagged as senior citizens, against ~16% in the reference population.
  • Contract type, normally the strongest churn predictor, spans under 6 percentage points.

The columns were generated independently. No amount of tuning would produce a model that generalises, and reporting a tuned figure would have been misleading.


2. Customer segmentation — and a parsing bug that invented a segment

02_customer_segmentation/notebooks/segmentation_analysis.ipynb

An earlier version of this analysis produced a compelling result: a large segment of high-income customers who browse extensively and never purchase, and an obvious recommendation to target them for conversion.

The segment did not exist.

ast.literal_eval returned a tuple for 33 of 50 rows and a dict for one more, because the source export omitted the enclosing brackets on those rows. A downstream isinstance(x, list) check then assigned all 34 zero purchases — silently, with no exception raised and no value missing. K-means grouped them together, and the artifact was interpreted as a customer behaviour.

Buggy Corrected
Mean purchases per record 0.68 2.22
Records at zero purchases 34 of 50 0 of 50
corr(time on site, purchases) −0.445 +0.344

Fixing the parser removes the segment and reverses the sign of the headline relationship.

The notebook also uses silhouette scores rather than eyeballing an elbow curve, and reports a detail worth noting: the buggy clustering scored higher (0.475) than the corrected one (0.368), because 34 identical artificial zeros are trivially separable. A quality metric improving is not evidence that the data is right.


3. Data quality auditing in SQL

03_sql_data_quality/notebooks/sql_audit.ipynb

The auditing half of project 1, done the way it would run against a real source system rather than in pandas. The flat extract is normalised back into the customer, service and billing tables it plausibly came from, so the checks have to join across them.

Uses joins, CTEs, CASE aggregation, HAVING, and window functions (NTILE, RANK, AVG OVER) to isolate dependency violations in 295 of 300 records, confirm total_charges is fully derived on every row, and show that churn rate carries no monotonic relationship to tenure decile or contract type. Same verdict as the Python analysis, reached independently.


Running these

python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
jupyter lab

Each notebook runs top to bottom from a fresh kernel with no manual steps. Data paths are relative, so the notebooks work from a clone without editing.

A note on the datasets

Both are small teaching datasets, and the point here is not to criticise their origin. They are what I had, and working with flawed data honestly seemed more useful than swapping in something clean. The checks demonstrated — dependency audits, derived-feature detection, permutation testing, parser-output validation — are the ones I would run before trusting any model, at any scale.

About

Three tabular analyses focused on data quality validation and honest reporting of negative results

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages