Updated October 2026 | 16 min read
If you want to learn data science with Python from scratch, this is the page to start on. Not a list of 40 things to learn someday — one small, real dataset that you load, clean, explore, visualize, and model, start to finish, with the actual output printed next to every step. By the end you’ll have run a complete data science project on your own machine and understood every line.
Everything here uses only four libraries — NumPy, Pandas, Matplotlib and scikit-learn — and a dataset small enough that you can check every number by hand. If you’re new to Python itself, go through our Python full PDF from beginner to advanced first. These notes are also available as a free PDF at the end.
What Data Science Actually Is (and Isn’t)
Data science is not “doing AI” and it’s not just making charts. It’s a repeatable process for turning raw data into a decision someone can act on. Every real project — whether it’s predicting house prices or figuring out why users leave an app — follows the same five stages: you collect the data, clean it (this is 60–70% of the real work), explore it to understand what’s there, model it to make predictions or find patterns, and finally communicate what you found in plain language. Machine learning is just the “model” stage. The other four are what separate a data scientist from someone who knows scikit-learn.

Setup
You need Python 3 installed and these four libraries. Install them all with one command:
$ pip install numpy pandas matplotlib scikit-learn
Then start every script with these imports:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
Step 1 — NumPy Basics
NumPy is the foundation everything else is built on. Its core object is the array — a fast list of numbers you can do math on all at once. Here’s the handful of operations you’ll use constantly:
scores = np.array([72, 85, 90, 65, 78])
print("Mean:", scores.mean())
print("Std :", round(scores.std(), 2))
print("Max :", scores.max())
print("Min :", scores.min())
print("Above 75:", scores[scores > 75])
Std : 8.92
Max : 90
Min : 65
Above 75: [85 90 78]
Two things to notice. First, scores.mean() works on the whole array at once — no loop needed. Second, scores[scores > 75] is called boolean indexing: it filters the array by a condition in one line. You’ll use this pattern in Pandas constantly.
Step 2 — Load Real Data with Pandas
Pandas is NumPy for tables. Its core object is the DataFrame — rows and columns with names, like a spreadsheet you can program. We’ll build a small dataset of students, their study hours and test scores. It’s deliberately tiny so you can verify every number by hand, and it deliberately contains two real-world problems we’ll fix in the next step: a missing value and a duplicate row.
data = {
"name": ["Ali", "Sara", "Ahmed", "Zara", "Bilal", "Sara", "Omar"],
"hours": [5, 8, 6, None, 9, 8, 4],
"score": [60, 85, 70, 65, 92, 85, 55],
"city": ["Lahore", "Karachi", "Lahore", "Islamabad",
"Karachi", "Karachi", "Lahore"]
}
df = pd.DataFrame(data)
print(df)
print("\nShape:", df.shape)
0 Ali 5.0 60 Lahore
1 Sara 8.0 85 Karachi
2 Ahmed 6.0 70 Lahore
3 Zara NaN 65 Islamabad
4 Bilal 9.0 92 Karachi
5 Sara 8.0 85 Karachi
6 Omar 4.0 55 Lahore
Shape: (7, 4)
Seven rows, four columns. Look closely: row 3 has NaN (missing hours), and rows 1 and 5 are identical — Sara appears twice. Real datasets have exactly these problems, just at scale.
Step 3 — Clean It: Missing Values, Duplicates, Types
This is the step beginners skip and professionals spend most of their time on. First, find the problems:
print("Missing values:\n", df.isnull().sum())
print("\nDuplicate rows:", df.duplicated().sum())
print("\nData types:\n", df.dtypes)
name 0
hours 1
score 0
city 0
dtype: int64
Duplicate rows: 1
Data types:
name object
hours float64
score int64
city object
dtype: object
One missing value in hours, one duplicate row. Note that hours became float64 instead of int64 — that’s because of the None; Pandas can’t store NaN in an integer column. (On pandas 3.0+ you’ll see str instead of object for the text columns — same thing, just a newer name.) Now fix both problems:
# 1. Remove the duplicate row
df = df.drop_duplicates()
# 2. Fill the missing hours with the column mean
df["hours"] = df["hours"].fillna(df["hours"].mean())
print(df)
print("\nShape after cleaning:", df.shape)
print("Missing values left:", df.isnull().sum().sum())
0 Ali 5.0 60 Lahore
1 Sara 8.0 85 Karachi
2 Ahmed 6.0 70 Lahore
3 Zara 6.4 65 Islamabad
4 Bilal 9.0 92 Karachi
6 Omar 4.0 55 Lahore
Shape after cleaning: (6, 4)
Missing values left: 0
Before: 7 rows, 1 missing, 1 duplicate. After: 6 rows, nothing missing. Zara’s hours became 6.4 — the mean of the other five students (5 + 8 + 6 + 9 + 4 = 32, divided by 5). Also notice the index jumps from 4 to 6: dropping a row doesn’t renumber the others. If that bothers you, add df = df.reset_index(drop=True).
Filling with the mean is the simplest strategy, not always the best one. For a real project you’d ask why the value is missing before deciding how to fill it. But “notice it, decide, document it” is the habit that matters.
Step 4 — Explore: describe(), groupby, Correlation
Now that the data is clean, let’s understand it. Three commands do most of the work:
# Summary statistics for numeric columns
print(df[["hours", "score"]].describe().round(2))
# Average score per city
print("\nMean score by city:")
print(df.groupby("city")["score"].mean().round(2))
# How strongly do hours and score move together?
print("\nCorrelation hours vs score:",
round(df["hours"].corr(df["score"]), 3))
count 6.00 6.00
mean 6.40 71.17
std 1.85 14.50
min 4.00 55.00
25% 5.25 61.25
50% 6.20 67.50
75% 7.60 81.25
max 9.00 92.00
Mean score by city:
city
Islamabad 65.00
Karachi 88.50
Lahore 61.67
Name: score, dtype: float64
Correlation hours vs score: 0.976
Reading this like a data scientist: average score is 71 with a wide spread (std 14.5, range 55–92). Karachi students score highest on average — but there are only two of them, so don’t over-read that. The number that matters is the correlation: 0.976. On a scale of −1 to 1, that’s a very strong positive relationship — students who study more score higher. That’s our signal, and it’s exactly what we’ll model next.
| Command | What it answers |
|---|---|
df.describe() |
What’s the shape of each column? (mean, spread, range) |
df.groupby("col") |
How does a metric differ across categories? |
df["a"].corr(df["b"]) |
Do two columns move together? (−1 to 1) |
Step 5 — Visualize: Histogram and Scatter
Numbers tell you what; charts show you why. Two charts cover most exploration. A histogram shows how one column is distributed; a scatter plot shows the relationship between two columns.
# Histogram — distribution of scores
plt.figure(figsize=(6, 4))
plt.hist(df["score"], bins=5, color="#4f46e5", edgecolor="white")
plt.title("Distribution of Test Scores")
plt.xlabel("Score")
plt.ylabel("Number of students")
plt.savefig("histogram.png", dpi=150, bbox_inches="tight")
plt.show()
# Scatter — hours vs score
plt.figure(figsize=(6, 4))
plt.scatter(df["hours"], df["score"], color="#059669", s=80)
plt.title("Study Hours vs Score")
plt.xlabel("Hours studied")
plt.ylabel("Score")
plt.savefig("scatter.png", dpi=150, bbox_inches="tight")
plt.show()


The scatter plot is the correlation of 0.976 made visible: the dots line up almost perfectly from bottom-left to top-right. When you see a pattern this clean, a straight line will model it well — which is exactly what linear regression does.
Step 6 — Your First Model: Linear Regression
A model is a function that predicts one column from others. Linear regression fits the best straight line through the data. We’ll predict score from hours:
from sklearn.linear_model import LinearRegression
X = df[["hours"]] # features (2D — note the double brackets)
y = df["score"] # target (1D)
model = LinearRegression()
model.fit(X, y)
print("Slope :", round(model.coef_[0], 2))
print("Intercept :", round(model.intercept_, 2))
print("R² score :", round(model.score(X, y), 3))
# Predict: a student who studies 7 hours
# (pass a DataFrame with the same column name — see Mistake 4)
new = pd.DataFrame({"hours": [7]})
print("7 hours → predicted score:",
round(model.predict(new)[0], 1))
Intercept : 22.35
R² score : 0.952
7 hours → predicted score: 75.7
What the model learned: score ≈ 7.63 × hours + 22.35. Every extra hour of study is worth about 7.6 points. The R² of 0.952 means the line explains 95% of the variation in scores — very high, because our data is clean and the relationship is strong. (On real messy data, 0.5–0.7 is often good.) And a quick sanity check: R² for simple linear regression equals the correlation squared — 0.976² = 0.952. The numbers agree, which is how you know you didn’t make a mistake.
One important caveat: we scored the model on the same data we trained it on. That’s fine for understanding the mechanics, but it’s a mistake on a real project — see Common Mistake #2 below for the fix.
Step 7 — Communicate: Writing Up Findings
The analysis is worthless if nobody understands it. A write-up has four parts, in this order:
- Question — one sentence. “Does study time predict test scores?”
- Answer — one sentence, with the number. “Yes — strongly. Each additional hour is worth about 7.6 points (R² = 0.95).”
- Evidence — one chart (the scatter plot) and one table (the describe output). Not ten.
- Caveats — what would change your conclusion. “Only 6 students; Karachi’s high average is based on 2 people; model not tested on unseen data.”
Lead with the answer, not the method. Nobody cares that you used groupby; they care what you found and whether they should trust it.
Common Mistakes (and the Exact Error You’ll See)
These four are the ones almost every beginner hits in their first week. Knowing the exact error text saves you an hour of searching each time.
Mistake 1: SettingWithCopyWarning
SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Cause: You filtered a DataFrame (e.g. high = df[df["score"] > 70]) and then changed a column on the filtered result. Pandas isn’t sure whether you meant to change the original or the copy.
Fix: Make the copy explicit: high = df[df["score"] > 70].copy(). Or change the original directly with df.loc[df["score"] > 70, "grade"] = "A".
Mistake 2: Scoring on training data (mixing train and test)
Cause: Testing the model on the same rows it learned from — like grading a student on the exact questions they practiced. The score looks great and means nothing about new data.
Fix: Split first, train on one part, score on the other:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42)
model.fit(X_train, y_train) # learn from this
print(model.score(X_test, y_test)) # judge on this
With only 6 rows this split is too small to be meaningful — but on any real dataset, this is non-negotiable.
Mistake 3: Taking the mean of a text column
TypeError: Could not convert ['AliSaraAhmed...'] to numeric
Cause: Calling df.mean() on a DataFrame that has text columns like name and city. Pandas tries to average strings and fails.
Fix: df.mean(numeric_only=True), or select the numeric columns first: df[["hours", "score"]].mean().
Mistake 4: “X does not have valid feature names”
UserWarning: X does not have valid feature names,
but LinearRegression was fitted with feature names
Cause: You trained the model on a DataFrame (which has a column name, hours) but then predicted with a plain list like model.predict([[7]]), which has no column name. scikit-learn warns you because it can’t confirm the columns line up — and on a multi-column model, that mismatch can silently give wrong answers.
Fix: Predict with a DataFrame that uses the same column name as training: model.predict(pd.DataFrame({"hours": [7]})). That’s what Step 6 does, which is why it runs clean.
Data Science Roadmap — What to Learn Next, in Order
| Stage | Learn | You can then… |
|---|---|---|
| 1 — Done | NumPy, Pandas, Matplotlib basics, linear regression | Run a full project on a small dataset |
| 2 | Statistics: distributions, hypothesis tests, confidence intervals | Say whether a result is real or noise |
| 3 | More models: logistic regression, decision trees, random forests | Classify, not just predict numbers |
| 4 | Model evaluation: train/test, cross-validation, precision/recall | Trust your results on unseen data |
| 5 | Real data: CSV/SQL loading, feature engineering, bigger datasets | Handle messy, real-world problems |
| 6 | Neural networks and deep learning | Images, text, and complex patterns |
For stage 6, our neural network from scratch in Python notes pick up exactly where these leave off — same style, tested code, real output.
Practice Exercises
- Add a column. Create
df["passed"] = df["score"] >= 65, then usegroupby("city")["passed"].mean()to get the pass rate per city. Which city has the highest? - Change the fill strategy. Re-run Step 3 but fill the missing hours with the median instead of the mean. Does the correlation in Step 4 change? By how much?
- Add a feature. Add a
sleep_hourscolumn with values of your choice and fit the model withX = df[["hours", "sleep_hours"]]. Does R² go up? (Careful: with 6 rows, more features can fit noise — that’s overfitting, and you’ll meet it again at Stage 4.)
Recommended Books and Free Resources
- Data Science from Scratch by Joel Grus (O’Reilly) — builds every tool from first principles in plain Python. The closest book in spirit to these notes. Buy it to support the author.
- Python for Data Analysis by Wes McKinney — written by the creator of Pandas, and the full 3rd edition is officially free online.
- Kaggle Learn — free, short, hands-on courses on Pandas, visualization and intro ML, with real datasets.
- scikit-learn tutorials — the official docs are genuinely good once you’ve done the steps above.
- For the AI side of the roadmap, see our AI from scratch for beginners PDF notes.
📥 Download These Notes as PDF
Want to work through this offline? Download the complete Data Science with Python from Scratch notes — all seven steps, every code block with its real output, the four common mistakes and fixes, the roadmap and the exercises — as a free PDF from TechProFree. No signup required.
Final Thoughts
You just did data science — not read about it. You loaded data, found and fixed real problems in it, explored it with statistics and charts, built a model that explains 95% of the variation, and learned how to write it up. That’s the entire pipeline, and every real project is this same loop, just with more rows and messier data.
Run the code, do the three exercises, then pick a dataset you actually care about and repeat the seven steps on it. That second project — on your own data — is where it clicks.
Which step tripped you up? Found a dataset you want to try this on? Drop a comment below — I read and reply to every one.
Frequently Asked Questions (FAQ)
Start with Python basics, then NumPy and Pandas for handling data, then Matplotlib for charts, then one simple model (linear regression) — which is exactly what these notes cover. After that, add statistics, more models (logistic regression, trees), proper evaluation with train/test splits, and finally neural networks. The roadmap table above lists the order.
To start, very little — you need to understand what a mean, standard deviation and correlation are, which these notes explain with real numbers. To go further you’ll want basic statistics (distributions, hypothesis testing) and, for machine learning, some linear algebra and the idea of a derivative. You can learn these alongside the code rather than before it.
Python. Both are capable, and R is excellent for statistics, but Python has the larger job market, the dominant machine learning libraries (scikit-learn, TensorFlow, PyTorch), and it’s a general-purpose language you can use for everything else too. If you’re starting from scratch in 2026, Python is the safer bet.
You can complete these notes and run your first full project in a day. Getting comfortable with Pandas and basic models takes about 2–3 months of regular practice. Reaching a job-ready level — including statistics, model evaluation and real messy data — typically takes 6–12 months, depending on how much time you put in and whether you work on real projects.
Yes. The complete notes — every step, all code with its real output, the four common mistakes, roadmap and exercises — are available as a free PDF download from TechProFree, linked above. No signup is required.


