Data Science And Machine Learning • Programming Books

Data Science with Python from Scratch: Complete Notes

Data Science with Python from Scratch

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.

data science with python from scratch – 5-stage pipeline diagram collect clean explore model communicate

Setup

You need Python 3 installed and these four libraries. Install them all with one command:

⬛ Shell
$ pip install numpy pandas matplotlib scikit-learn

Then start every script with these imports:

🐍 Python
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:

🐍 Python
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])
Mean: 78.0
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.

🐍 Python
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)
name hours score city
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:

🐍 Python
print("Missing values:\n", df.isnull().sum())
print("\nDuplicate rows:", df.duplicated().sum())
print("\nData types:\n", df.dtypes)
Missing values:
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:

🐍 Python
# 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())
name hours score city
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:

🐍 Python
# 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))
hours score
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.

🐍 Python
# 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()

data science with python from scratch – matplotlib histogram of student test scores

data science with python from scratch – scatter plot of study hours vs score showing strong positive correlation

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:

🐍 Python
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))
Slope : 7.63
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

⬛ Warning
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:

🐍 Python
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

⬛ Error
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”

⬛ Warning
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 use groupby("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_hours column with values of your choice and fit the model with X = 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

📥 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)

 

What is the best roadmap to learn data science with Python from scratch?

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.

How much math do I need for data science?

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.

Should I learn Python or R for data science?

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.

How long does it take to learn data science from scratch?

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.

Can I download these data science notes as a PDF?

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.