Python Data Science Tutorial: Learn Data Science from Scratch (2026)
My first data science project was predicting apartment prices in my city — I had a CSV of listings, a Jupyter notebook, and no idea what I was doing. That project taught me that data science isn't about fancy algorithms; it's about asking the right question, cleaning the data, exploring patterns, and communicating results clearly. The Python stack — Pandas, NumPy, Matplotlib/Seaborn, and scikit-learn — covers the entire workflow from raw data to model evaluation.
This tutorial walks through an end-to-end data science project on a customer churn dataset. You'll perform exploratory data analysis (EDA), feature engineering, model building, and interpretation. The emphasis is on the decision-making process: why to choose a certain visualization, which transformation to apply, and how to interpret model coefficients.
Exploratory Data Analysis and Summary Statistics
EDA is the first step in any data project. I load the data, check shapes, data types, missing values, and basic statistics using df.describe(). Histograms and box plots reveal distributions and outliers. Pair plots show relationships between numeric variables. The goal is to understand what you're working with before any modeling.
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
df = pd.read_csv('customer_churn.csv')
print(df.shape)
print(df.info())
print(df.describe())
print(df['churn'].value_counts(normalize=True))
df.hist(figsize=(12, 10), bins=30)
plt.tight_layout()
plt.show()
Handling Missing Values and Outliers
Missing data needs a decision: drop rows if few records are affected, impute with median (robust to outliers) or mean, or encode 'missing' as its own category for categorical features. Outliers distort models like linear regression. I cap extreme values at percentiles or transform with log to reduce skew.
print(df.isnull().sum() / len(df) * 100)
for col in ['tenure', 'monthly_charges']:
df[col].fillna(df[col].median(), inplace=True)
cap = df['total_charges'].quantile(0.99)
df['total_charges'] = df['total_charges'].clip(upper=cap)
import numpy as np
df['log_tenure'] = np.log1p(df['tenure'])
Feature Engineering and Encoding
Feature engineering transforms raw data into informative predictors. For customer churn, I create features like average monthly spend per tenure, number of support tickets, and whether the customer has multiple services. Categorical variables are encoded: one-hot for nominal categories and ordinal encoding for ordered ones.
df['avg_monthly_spend'] = df['total_charges'] / df['tenure'].replace(0, 1)
df['has_multiple_services'] = (
(df['phone_service'] == 'Yes') & (df['internet_service'] != 'No')
).astype(int)
df = pd.get_dummies(df, columns=['contract', 'payment_method'], drop_first=True)
education_map = {'High School': 0, 'Associate': 1, 'Bachelor': 2, 'Master': 3, 'Doctorate': 4}
df['education_level'] = df['education'].map(education_map)
Model Building: Logistic Regression Baseline
I always start with a simple, interpretable model as a baseline. Logistic regression gives coefficient estimates that describe each feature's impact on churn probability. Standardize numeric features first since coefficients are sensitive to scale. The baseline accuracy, precision, recall, and ROC AUC establish the bar.
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, roc_auc_score
X = df.drop('churn', axis=1)
y = df['churn']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
lr = LogisticRegression(max_iter=1000)
lr.fit(X_train_scaled, y_train)
y_pred = lr.predict(X_test_scaled)
y_proba = lr.predict_proba(X_test_scaled)[:, 1]
print(classification_report(y_test, y_pred))
print(f"ROC AUC: {roc_auc_score(y_test, y_proba):.4f}")
Model Comparison: Random Forest and Gradient Boosting
Tree-based models capture non-linear relationships and interactions without manual feature engineering. Random Forest averages many deep trees to reduce overfitting. Gradient Boosting (XGBoost, LightGBM) builds trees sequentially, each correcting previous errors. I compare multiple models using cross-validated ROC AUC.
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier
rf = RandomForestClassifier(n_estimators=200, max_depth=8, random_state=42)
rf.fit(X_train, y_train)
rf_auc = roc_auc_score(y_test, rf.predict_proba(X_test)[:, 1])
print(f"Random Forest AUC: {rf_auc:.4f}")
xgb = XGBClassifier(n_estimators=200, max_depth=6, learning_rate=0.05, random_state=42)
xgb.fit(X_train, y_train)
xgb_auc = roc_auc_score(y_test, xgb.predict_proba(X_test)[:, 1])
print(f"XGBoost AUC: {xgb_auc:.4f}")
importances = pd.Series(rf.feature_importances_, index=X.columns)
importances.sort_values(ascending=False).head(10).plot(kind='bar')
Communicating Results and Model Interpretation
A model is only valuable if stakeholders trust and act on it. SHAP values explain individual predictions by showing which features drove a customer's churn score. Partial dependence plots show how a feature affects predictions on average. I present findings with a one-page executive summary and the top-5 features driving churn.
import shap
explainer = shap.TreeExplainer(xgb)
shap_values = explainer.shap_values(X_test)
shap.summary_plot(shap_values, X_test, max_display=10)
shap.force_plot(explainer.expected_value, shap_values[0], X_test.iloc[0])
Frequently Asked Questions
Do I need a degree in statistics to do data science?
No. You need practical statistics: descriptive stats, hypothesis testing, regression, and probability. The math behind most ML algorithms is less important than knowing when to use them and how to evaluate them.
Should I use Jupyter Notebook or a Python script?
Jupyter is ideal for exploration, visualization, and storytelling. Scripts are better for production code, testing, and automation. I start in Jupyter, then refactor the stable parts into Python modules.
What if my dataset is too large for Pandas?
Use Polars (faster, lower memory), Dask (distributed Pandas), or chunked processing with Pandas. For very large datasets, consider sampling before exploration.
How do I deal with data leakage?
Never use information from the test set in training. This includes scaling before split, using target encoding before split, or using future data to predict the past. Always split first, then transform using only training statistics.
Originally published on Ayodhyyya. Last updated June 1, 2026.