LambdaLabTM
Artificial Intelligence · Class 10 Project · Subject Code 417
Sample ProjectStatistical DataSDG 2 · SDG 15⏱️ 15 min read

Which Crop for This Field?

Soil nutrients, pH and rainfall in; the crop that suits the field out — a model that asks the five most similar fields what grew well there.

1Introduction

A farmer can get the soil tested. The report gives numbers: nitrogen, phosphorus, potassium and pH. But a sheet of numbers does not say what to sow. Reading it well needs an expert, and there are far more farmers than experts.

So many farmers sow what they sowed last year, or what the neighbour sows. If the crop does not suit the soil and the rainfall, the harvest is poor, and more fertiliser is used to make up for it.

This project asks: given a field's soil test and its weather, can a computer recommend a crop that suits it?

2SDG 2
Zero Hunger
15SDG 15
Life on Land
how it connects to the goal

SDG 2 asks for more food from the land we already farm, by small farmers above all. A crop that suits the soil gives a better harvest with less fertiliser and less water. That also protects the soil for the next season, which is what SDG 15 is about.

The curriculum asks that the project be related to the Sustainable Development Goals. That link is the reason the problem is worth solving, so it belongs at the very start of the report, not tacked on at the end.

2Stage 1 — Problem Scoping

Problem scoping means understanding the problem properly before building anything. The tool for it is the 4Ws Problem Canvas: who has the problem, what the problem is, where it happens, and why solving it matters.

Who?the stakeholders
  • Small farmers who have a soil test report
  • Krishi Vigyan Kendra and agriculture department staff
What?the problem
  • A soil test gives numbers, not a decision
  • The wrong crop gives a poor harvest and wastes fertiliser and water
Where?the context
  • Farms in villages with few agriculture experts
  • At sowing time, when the decision has to be made quickly
Why?the benefit
  • The right crop gives more food from the same field
  • Less fertiliser is wasted and the soil stays healthy

The problem statement

The four answers are then joined into one sentence, using the curriculum's problem statement template:

Our small farmers have a problem of choosing a crop that suits their soil when they have a soil test report but no expert to explain it. An ideal solution would recommend a suitable crop straight from the soil test and the weather.
who would use it

A small farmer with a soil test report, or a Krishi Vigyan Kendra worker helping many farmers at once.

How it is done today

The Soil Health Card

The government's Soil Health Card gives a farmer the soil test results and fertiliser advice. It is a big help, but the farmer still has to decide what to sow.

Advice from an expert

An agriculture officer can read the report and advise. There are few of them, and a farmer may have to travel and wait.

Sowing what was sown last year

The most common way. It needs no help, but it ignores what the soil test says, and the same crop year after year can wear out the soil.

Objectives

  1. To read soil test results and weather for 500 fields from a CSV file
  2. To clean the data: remove repeated rows and missing readings, and make every crop name the same
  3. To find, with charts, which features separate one crop from another
  4. To build a k nearest neighbours model that recommends a crop
  5. To test it on fields it has never seen, and find which crops it mixes up
  6. To see how the number of neighbours (k) changes the result
  7. To recommend crops for new soil test cards

📘 Revise: Stage 1 — Problem Scoping

3Stage 2 — Data Acquisition

Each row of the file is one field. It holds the field's soil test (nitrogen, phosphorus and potassium in kg per hectare, and pH), the average rainfall and temperature of the growing season, and the crop that grew well there. That last column is the label.

The file has 503 rows. They cover five crops: rice, wheat, maize, chickpea and cotton.

For a real project, soil test results can be collected from farmers' Soil Health Cards (with their permission), and rainfall and temperature from the India Meteorological Department. Public crop recommendation datasets can also be found on open data websites — if you use one, give its link in your report.

Watch Out
This dataset is a LambdaLab sample. A program made it to behave like the real thing, so the project can be run before you have collected anything. It is not real data. In your own report, use data you collected or a public dataset, and say where it came from.

Features and label

The features are what the model is given. The label is the answer it has to learn to give.

ColumnTypeWhat it holdsRole
nitrogenintegerNitrogen (N) in the soil, kg per hectarefeature
phosphorusintegerPhosphorus (P) in the soil, kg per hectarefeature
potassiumintegerPotassium (K) in the soil, kg per hectarefeature
phdecimalHow acidic or alkaline the soil is (7 is neutral)feature
rainfall_mmdecimalAverage rainfall per month in the growing season, mmfeature
temperature_cdecimalAverage temperature in the growing season, °Cfeature
croptextThe crop that grew well in this field — what the model must recommendlabel
sample_notextAn ID for the soil sample. No farmer's name is stored.not used

The first eight rows of soil_samples.csv

sample_nonitrogenphosphoruspotassiumphrainfall_mmtemperature_ccrop
S10018963436.869.918.4Wheat
S10025765837.276.816.4Chickpea
S10032367797.171.319.4Chickpea
S10044459787.579.419.4Chickpea
S100511149396.960.714.4Wheat
S10069548466.972.718.5Wheat
S10069548466.972.718.5Wheat
S100710735426.981.019.1Wheat

Inside soil_samples.csv

Five hundred fields, each with its soil test, its weather and the crop that grew well there. It has the mistakes real typed data has. The whole file is 503 rows, 17.8 KB. It comes with the project download, and you can take the file on its own.

📘 Revise: Stage 2 — Data Acquisition

4Stage 3 — Data Exploration

Data exploration means getting to know the data before trusting it: fixing what is wrong with it, then drawing it to see the patterns. The charts here are what decide what the model should be.

What was wrong with the data

The same sample typed in twice
3 rows

drop_duplicates() removes an exact repeat. Left in, that field would get two votes when the model asks its neighbours.

pH left blank
4 (removed)

The rows are removed and counted. Guessing a pH would invent a soil test nobody did.

Crop names typed differently
"rice", " WHEAT", "maize "

strip() removes the spaces and title() makes the capitals the same. Without this, "rice" and "Rice" would be two different crops.

Features on very different scales
rainfall in hundreds, pH under 10

Each feature is rescaled to run from 0 to 1 before fields are compared, so rainfall does not decide everything just because its numbers are bigger.

1Average rainfall for each crop
Bar chart
Average rainfall for each crop
how to read it

One bar per crop. The height is the average rainfall of the fields where that crop grew well.

what it shows

Rice stands far above the rest, at about three times the rainfall of any other crop. Rainfall alone picks out rice. The other four crops all grew with similar rain, so something else must tell them apart.

drawn by the program · saved as chart1_rainfall.png
2Rainfall and temperature of every field
Scatter plot
Rainfall and temperature of every field
how to read it

Each dot is one field: rainfall across, temperature up, coloured by its crop.

what it shows

Rice sits alone on the right. Wheat sits low, in the coolest fields — it is a winter crop. Chickpea, maize and cotton overlap in the middle, so the weather cannot separate them. The soil has to.

drawn by the program · saved as chart2_rain_temperature.png
3Nitrogen, phosphorus and potassium for each crop
Bar chart
Nitrogen, phosphorus and potassium for each crop
how to read it

Three bars for each crop: the average nitrogen, phosphorus and potassium in its soil.

what it shows

This is where the middle group comes apart. Chickpea grows with little nitrogen but a lot of potassium — it is a pulse, and pulses add their own nitrogen to the soil. Cotton has the most nitrogen of all. Maize and cotton look alike in phosphorus and potassium, so these two are the pair to watch in the evaluation.

drawn by the program · saved as chart3_npk.png

📘 Revise: Stage 3 — Data Exploration

5Stage 4 — Modelling

Where this model sits in the family of AI models:

The answer is one of five crops, so this is classification. Every field in the data comes with the crop that grew well, so the model learns from labelled examples: supervised learning.

How the model works

The data is shuffled and split into 396 training fields and 100 testing fields.

The model is called k nearest neighbours, and it works the way a farmer might ask around. To recommend a crop for a new field, it measures how close that field's soil and weather are to every training field. It takes the 5 closest — the 5 nearest neighbours — and recommends the crop most of them grew.

Before measuring how close two fields are, every feature is rescaled to run from 0 to 1. Otherwise a difference of 50 mm of rain would count for far more than a difference of 1 in pH, though the pH difference matters more to the crop.

📘 Revise: Stage 4 — Modelling

6Stage 5 — Evaluation

On the 100 testing fields the model got 97 of 100 right — an accuracy of 97.0 %.

The confusion matrix shows where the mistakes are, and precision and recall per crop say how bad each one is. Maize has the lowest recall, 86.7%: of the 15 maize fields, two were recommended cotton. For a farmer that is the mistake that matters — sowing cotton where maize would do better.

Which number matters most? Here a wrong recommendation of any kind costs a farmer a season. So overall accuracy is a fair summary, but the per-crop recall is what the report should be honest about: the model is very reliable for rice, wheat and chickpea, and less sure between maize and cotton.

4Confusion matrix
Confusion matrix
Confusion matrix
how to read it

Rows are the crop that really grew well. Columns are the crop the model recommended. The diagonal holds the correct answers.

what it shows

Rice, wheat and chickpea were never wrong. All three mistakes are between maize and cotton — the same pair chart 3 warned about. The model is not making random errors; it is struggling exactly where the data overlaps.

drawn by the program · saved as chart4_confusion_matrix.png
5How many neighbours should the model ask?
Line chart
How many neighbours should the model ask?
how to read it

Across is k, the number of neighbours asked. Up is the accuracy on the testing fields for that k.

what it shows

Asking only one neighbour (k = 1) gave 96.0 %. One unusual field can mislead it. From about three neighbours the line levels out and stays high, so k = 5 is a safe choice — enough neighbours to outvote one odd field.

drawn by the program · saved as chart5_choosing_k.png

📘 Revise: The Confusion Matrix

7Stage 6 — Deployment

The program ends by reading three new soil test cards and recommending a crop for each. With each answer it prints how many of the 5 neighbours agreed. 5 of 5 means the model is sure; 3 of 5 means the field is near a boundary and an expert should look.

In real use this could sit inside a Krishi Vigyan Kendra: the soil test is typed in and the recommendation is printed on the farmer's card, with the agreement count beside it.

📘 Revise: Stage 6 — Deployment

8Ethical concerns

An AI project is not finished when the model works. The curriculum asks three questions of every model — is it fair (bias), can it explain itself (transparency), and who answers for its mistakes (accountability). For this project:

Who is responsible for a bad harvest?

The model recommends; the farmer decides. If it is wrong, the farmer loses a season, not the computer. That is why it prints how sure it is, and why an expert should check any field where the neighbours disagree. This is accountability.

Bias in the data

The model only knows five crops and the conditions in its data. A field with a soil the data never saw — very acidic, or very salty — will still get one of the five crops, even if none suits it. The report must say this.

Farmers' data

Soil tests belong to the farmers who paid for them. A real dataset should use sample numbers, never names, and be collected with permission.

📘 Revise: Bias, Transparency, Accountability

9Source code

The whole program — it is also in the downloads at the top of this page — in Python with pandas, NumPy and matplotlib — the three libraries of Unit 7. It is laid out as the six stages of the AI Project Cycle, one after another. Every chart on this page was drawn by it, and every number came out of running it.

crop_recommendation.py
# ---------------------------------------------------------------------------
# crop_recommendation.py
#
# AI project on Statistical Data: recommending a crop for a field from its
# soil test and its weather.
#
# CBSE Class X, Artificial Intelligence (417), Part C project work.
# SDG 2 (Zero Hunger) and SDG 15 (Life on Land).
#
# The program follows the six stages of the AI Project Cycle. The model is
# "k nearest neighbours": to recommend a crop for a new field, it finds the
# k fields in the training data whose soil and weather are most like it, and
# recommends the crop that most of those neighbours grew well.
#
# Libraries: pandas (the data), numpy (the arithmetic), matplotlib (charts).
# ---------------------------------------------------------------------------

# %% Stage 1 - Problem scoping
print("=" * 60)
print("CROP RECOMMENDATION FROM A SOIL TEST")
print("=" * 60)
print("Who   : small farmers deciding what to sow")
print("What  : the wrong crop for the soil means a poor harvest")
print("Where : farms that have a soil test but no expert to read it")
print("Why   : the right crop gives more food from the same land")
print()

# %% Stage 2 - Data acquisition
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

pd.set_option("display.width", 120)
pd.set_option("display.max_columns", 20)

data = pd.read_csv("soil_samples.csv")

print("--- Stage 2: Data acquisition ---")
print("Rows read    :", len(data))
print("Columns read :", len(data.columns))
print()
print("The first 10 rows:")
print(data.head(10))
print()

# %% Stage 3 - Data exploration
print("--- Stage 3: Data exploration ---")

features = ["nitrogen", "phosphorus", "potassium", "ph", "rainfall_mm", "temperature_c"]
label = "crop"

# The same sample typed in twice would count twice, so repeats are removed.
before = len(data)
data = data.drop_duplicates()
print("Repeated rows removed        :", before - len(data))

# A soil test with a reading missing cannot be compared with other fields.
missing = data[features].isnull().any(axis=1).sum()
data = data.dropna(subset=features)
print("Rows with a reading missing  :", missing, "(removed)")

# "rice", " RICE" and "Rice" must all be one crop.
data[label] = data[label].str.strip().str.title()
print("Rows left                    :", len(data))
print()

crops = sorted(data[label].unique())
print("Fields of each crop:")
print(data[label].value_counts().sort_index())
print()

rain = data["rainfall_mm"]
print("Rainfall - mean   :", round(np.mean(rain), 1), "mm")
print("Rainfall - median :", round(np.median(rain), 1), "mm")
print()

averages_by_crop = data.groupby(label)[features].mean().round(1)
print("The average field for each crop:")
print(averages_by_crop)
print()

colours = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd"]

# Chart 1: average rainfall for each crop.
plt.figure(figsize=(7, 4))
plt.bar(crops, averages_by_crop["rainfall_mm"], color=colours)
plt.title("Average rainfall where each crop grew well")
plt.xlabel("Crop")
plt.ylabel("Rainfall (mm a month)")
plt.tight_layout()
plt.savefig("chart1_rainfall.png")
plt.close()

# Chart 2: rainfall against temperature, one colour per crop.
plt.figure(figsize=(7, 5))
for i in range(len(crops)):
    rows = data[data[label] == crops[i]]
    plt.scatter(rows["rainfall_mm"], rows["temperature_c"],
                color=colours[i], label=crops[i], alpha=0.7, s=18)
plt.title("Rainfall and temperature of every field")
plt.xlabel("Rainfall (mm a month)")
plt.ylabel("Temperature (deg C)")
plt.legend()
plt.grid(True, linestyle="--", alpha=0.4)
plt.tight_layout()
plt.savefig("chart2_rain_temperature.png")
plt.close()

# Chart 3: the three nutrients, side by side for each crop.
x = np.arange(len(crops))
plt.figure(figsize=(8, 4.5))
plt.bar(x - 0.27, averages_by_crop["nitrogen"], width=0.27, label="Nitrogen (N)", color="#4e79a7")
plt.bar(x, averages_by_crop["phosphorus"], width=0.27, label="Phosphorus (P)", color="#f28e2b")
plt.bar(x + 0.27, averages_by_crop["potassium"], width=0.27, label="Potassium (K)", color="#59a14f")
plt.xticks(x, crops)
plt.title("Average N, P and K in the soil for each crop")
plt.ylabel("kg per hectare")
plt.legend()
plt.tight_layout()
plt.savefig("chart3_npk.png")
plt.close()

# %% Stage 4 - Modelling
print("--- Stage 4: Modelling ---")

# Train-test split: shuffle (the same way every run), then 80% / 20%.
data = data.sample(frac=1, random_state=3)
cut = int(len(data) * 0.8)
train = data[:cut]
test = data[cut:]
print("Training rows :", len(train))
print("Testing rows  :", len(test))

# Rainfall runs into hundreds and pH stays under 10. Each feature is rescaled
# to run from 0 to 1 (using the training data only), so that no feature wins
# just because its numbers are bigger.
low = train[features].min()
high = train[features].max()
train_scaled = (train[features] - low) / (high - low)
test_scaled = (test[features] - low) / (high - low)

k = 5
print("Neighbours asked (k) :", k)
print()

predictions = []
for i in range(len(test)):
    row = test_scaled.iloc[i]
    # Distance from this field to EVERY training field.
    distance = ((train_scaled - row) ** 2).sum(axis=1)
    # The k closest fields, and the crop most of them grew.
    nearest = distance.nsmallest(k).index
    predictions.append(train.loc[nearest, label].mode()[0])

# %% Stage 5 - Evaluation
print("--- Stage 5: Evaluation ---")
actual = list(test[label])

correct = 0
for i in range(len(actual)):
    if predictions[i] == actual[i]:
        correct = correct + 1
accuracy = correct / len(actual) * 100
print("Correct recommendations :", correct, "of", len(actual))
print("Accuracy                :", round(accuracy, 1), "%")
print()

matrix = pd.crosstab(pd.Series(actual, name="Actual"),
                     pd.Series(predictions, name="Predicted"))
matrix = matrix.reindex(index=crops, columns=crops, fill_value=0)
print("Confusion matrix:")
print(matrix)
print()

print("Crop        Precision   Recall")
for c in crops:
    tp = matrix.loc[c, c]
    if matrix[c].sum() > 0:
        precision = tp / matrix[c].sum() * 100
    else:
        precision = 0
    if matrix.loc[c].sum() > 0:
        recall = tp / matrix.loc[c].sum() * 100
    else:
        recall = 0
    print(c.ljust(10), str(round(precision, 1)).rjust(9), "%", str(round(recall, 1)).rjust(7), "%")
print()

# Chart 4: the confusion matrix.
plt.figure(figsize=(6.5, 5.5))
plt.imshow(matrix.values, cmap="Greens")
for r in range(len(crops)):
    for c in range(len(crops)):
        value = matrix.values[r][c]
        if value > matrix.values.max() / 2:
            ink = "white"
        else:
            ink = "black"
        plt.text(c, r, value, ha="center", va="center", fontsize=13, color=ink)
plt.xticks(range(len(crops)), crops)
plt.yticks(range(len(crops)), crops)
plt.xlabel("Recommended crop")
plt.ylabel("Crop that grew well")
plt.title("Confusion matrix (testing data)")
plt.tight_layout()
plt.savefig("chart4_confusion_matrix.png")
plt.close()

# How much does the choice of k matter? Try every k from 1 to 15.
k_values = list(range(1, 16))
k_accuracy = []
for kk in k_values:
    right = 0
    for i in range(len(test)):
        distance = ((train_scaled - test_scaled.iloc[i]) ** 2).sum(axis=1)
        nearest = distance.nsmallest(kk).index
        if train.loc[nearest, label].mode()[0] == actual[i]:
            right = right + 1
    k_accuracy.append(right / len(test) * 100)

print("Accuracy for k = 1 :", round(k_accuracy[0], 1), "%")
print("Accuracy for k = 5 :", round(k_accuracy[4], 1), "%")
print("Accuracy for k = 15 :", round(k_accuracy[14], 1), "%")
print()

# Chart 5: accuracy for each value of k.
plt.figure(figsize=(7, 4))
plt.plot(k_values, k_accuracy, marker="o", color="#2ca02c")
plt.title("How many neighbours should the model ask?")
plt.xlabel("k (number of neighbours)")
plt.ylabel("Accuracy on testing data (%)")
plt.xticks(k_values)
plt.ylim(80, 100)
plt.grid(True, linestyle="--", alpha=0.4)
plt.tight_layout()
plt.savefig("chart5_choosing_k.png")
plt.close()

# %% Stage 6 - Deployment
print("--- Stage 6: Deployment ---")
print("Three new soil test cards:")

new_fields = pd.DataFrame({
    "nitrogen":      [85, 38, 120],
    "phosphorus":    [45, 70, 44],
    "potassium":     [41, 79, 19],
    "ph":            [6.2, 7.4, 7.1],
    "rainfall_mm":   [240, 78, 82],
    "temperature_c": [25, 19, 24],
})
new_scaled = (new_fields - low) / (high - low)

for i in range(len(new_fields)):
    distance = ((train_scaled - new_scaled.iloc[i]) ** 2).sum(axis=1)
    nearest = distance.nsmallest(k).index
    votes = train.loc[nearest, label]
    best = votes.mode()[0]
    agree = (votes == best).sum()
    f = new_fields.iloc[i]
    print("N", int(f["nitrogen"]), " P", int(f["phosphorus"]), " K", int(f["potassium"]),
          " pH", f["ph"], " rain", f["rainfall_mm"], "mm  ->", best,
          "(" + str(agree), "of", k, "neighbours agree)")

The library calls it is built from

CallWhereWhat it does
pd.read_csv()Stage 2Reads the CSV file into a table
drop_duplicates()Stage 3Removes rows typed in twice
dropna(subset=...)Stage 3Removes rows with a reading missing
str.strip().str.title()Stage 3Makes "rice " and "RICE" into Rice
groupby(...).mean()Stage 3The average field for each crop
plt.bar / plt.scatter / plt.plotStages 3, 5Bar charts, a scatter plot and a line chart
sample(frac=1, random_state=3)Stage 4Shuffles the rows the same way every run
(x - low) / (high - low)Stage 4Rescales a feature to run from 0 to 1
nsmallest(k).indexStage 4Finds the k closest training fields
mode()[0]Stage 4The crop most of the neighbours grew
pd.crosstab()Stage 5Builds the confusion matrix
plt.imshow / plt.textStage 5Draws the confusion matrix

10Sample output

A real run, exactly as it appeared. Nothing below was typed by hand.

Output
============================================================
CROP RECOMMENDATION FROM A SOIL TEST
============================================================
Who   : small farmers deciding what to sow
What  : the wrong crop for the soil means a poor harvest
Where : farms that have a soil test but no expert to read it
Why   : the right crop gives more food from the same land

--- Stage 2: Data acquisition ---
Rows read    : 503
Columns read : 8

The first 10 rows:
  sample_no  nitrogen  phosphorus  potassium   ph  rainfall_mm  temperature_c      crop
0     S1001        89          63         43  6.8         69.9           18.4     Wheat
1     S1002        57          65         83  7.2         76.8           16.4  Chickpea
2     S1003        23          67         79  7.1         71.3           19.4  Chickpea
3     S1004        44          59         78  7.5         79.4           19.4  Chickpea
4     S1005       111          49         39  6.9         60.7           14.4     Wheat
5     S1006        95          48         46  6.9         72.7           18.5     Wheat
6     S1006        95          48         46  6.9         72.7           18.5     Wheat
7     S1007       107          35         42  6.9         81.0           19.1     Wheat
8     S1008        78          41         38  7.8         54.2           17.3     Wheat
9     S1009       100          49         42  6.6        194.4           23.7      Rice

--- Stage 3: Data exploration ---
Repeated rows removed        : 3
Rows with a reading missing  : 4 (removed)
Rows left                    : 496

Fields of each crop:
crop
Chickpea     97
Cotton       99
Maize       101
Rice         99
Wheat       100
Name: count, dtype: int64

Rainfall - mean   : 110.6 mm
Rainfall - median : 83.5 mm

The average field for each crop:
          nitrogen  phosphorus  potassium   ph  rainfall_mm  temperature_c
crop                                                                      
Chickpea      38.6        68.0       80.1  7.1         79.6           19.0
Cotton       117.8        45.4       20.1  7.0         79.4           23.8
Maize         77.2        48.4       20.4  6.2         83.0           22.5
Rice          80.2        46.9       40.7  6.3        235.2           24.2
Wheat         99.0        49.8       40.2  6.9         76.0           16.9

--- Stage 4: Modelling ---
Training rows : 396
Testing rows  : 100
Neighbours asked (k) : 5

--- Stage 5: Evaluation ---
Correct recommendations : 97 of 100
Accuracy                : 97.0 %

Confusion matrix:
Predicted  Chickpea  Cotton  Maize  Rice  Wheat
Actual                                         
Chickpea         19       0      0     0      0
Cotton            0      23      1     0      0
Maize             0       2     13     0      0
Rice              0       0      0    27      0
Wheat             0       0      0     0     15

Crop        Precision   Recall
Chickpea       100.0 %   100.0 %
Cotton          92.0 %    95.8 %
Maize           92.9 %    86.7 %
Rice           100.0 %   100.0 %
Wheat          100.0 %   100.0 %

Accuracy for k = 1 : 96.0 %
Accuracy for k = 5 : 97.0 %
Accuracy for k = 15 : 97.0 %

--- Stage 6: Deployment ---
Three new soil test cards:
N 85  P 45  K 41  pH 6.2  rain 240.0 mm  -> Rice (5 of 5 neighbours agree)
N 38  P 70  K 79  pH 7.4  rain 78.0 mm  -> Chickpea (5 of 5 neighbours agree)
N 120  P 44  K 19  pH 7.1  rain 82.0 mm  -> Cotton (5 of 5 neighbours agree)

11The same project without code

The curriculum's Statistical Data unit uses Orange, and its kNN widget is the same idea as this program: ask the nearest neighbours. The tool is Orange Data Mining.

1
File

Load soil_samples.csv. Set crop as the target and sample_no as a meta attribute, so it is not used as a feature.

2
Data Table

Check the rows and spot the repeats and blanks.

3
Preprocess

Remove rows with missing values, and normalise the features to the 0–1 range.

4
Box Plot

Choose rainfall_mm and split it by crop to see rice stand apart — the same finding as chart 1.

5
Data Sampler

Keep 80% for training; the remaining 20% is the testing data.

6
kNN

Set the number of neighbours to 5. This is the model.

7
Test and Score

Test on the testing data and read the accuracy (CA), precision and recall.

8
Confusion Matrix

See which crops are mixed up — it should again be maize and cotton.

Tip
Doing both is the strongest report. The no-code version shows you can use the curriculum's tool; the Python version shows you understand what the tool is doing underneath. If the numbers come out slightly different, that is expected — the tool picks its own training rows and its own model settings — and saying why in the report is worth marks.

📘 Revise: No-Code AI, and Orange

12Testing

Every case below was actually run, and the result is what came back. Each one gives the whole program a small input of its own.

Test caseExpectedActualResult
The full datasetRows read : 503Rows read : 503Pass
Three rows typed in twice are removedRepeated rows removed : 3Repeated rows removed : 3Pass
Four soil tests with no pH are removedRows with a reading missing : 4 (removed)Rows with a reading missing : 4 (removed)Pass
rice, " WHEAT" and "maize " join their crops: still five cropsCorrect recommendations : 97 of 100Correct recommendations : 97 of 100Pass
Two crops with nothing in common: every recommendation rightAccuracy : 100.0 %Accuracy : 100.0 %Pass
One row pasted twice is counted onceRepeated rows removed : 1Repeated rows removed : 1Pass
"rice " is cleaned to Rice, so it does not become a sixth cropAccuracy : 100.0 %Accuracy : 100.0 %Pass
A soil test with the pH left blank is removedRows with a reading missing : 1 (removed)Rows with a reading missing : 1 (removed)Pass
The data file is missingFileNotFoundError: [Errno 2] No such file or directory: 'soil_samples.csv'FileNotFoundError: [Errno 2] No such file or directory: 'soil_samples.csv'Pass

13Results, limits and what next

what the project found
  • The model recommended the right crop for 97 of 100 testing fields — an accuracy of 97.0 %.
  • Rainfall alone separates rice; temperature separates wheat, a winter crop.
  • Chickpea stands out by its soil: little nitrogen, a lot of potassium.
  • Every mistake was between maize and cotton, whose soils are most alike.
  • Asking one neighbour is less reliable than asking five.

What this model cannot do

  • It knows only five crops
  • It does not know the market price, so it cannot say which crop will earn the most
  • Rainfall is a season's average; a late monsoon is not part of the data
  • A field unlike any in the data still gets one of the five crops

What to add next

This is where you make the project your own. Take one of these, or an idea of yours:

  • Add more crops, and a sixth feature such as humidity
  • Print "ask an expert" whenever fewer than 4 of the 5 neighbours agree
  • Collect real soil test results from your area, with permission, and retrain
  • Add the expected market price, so the farmer sees the likely earnings too

What you may have to learn on your own

  • What k nearest neighbours does, and why the features must be rescaled first
  • Reading a 5 × 5 confusion matrix
  • What N, P, K and pH mean for a crop — a short talk with an agriculture teacher helps
  • Building the same workflow with Orange's kNN widget

14Conclusion

The project shows that a soil test and the season's weather are enough to recommend a suitable crop most of the time. The model was right for 97 of 100 testing fields.

Data exploration did more than draw pictures. The three charts showed which features separate which crops, and they predicted the one place the model would struggle — maize and cotton — before the model was built.

A model like this should help a farmer, not replace advice. Printing how many neighbours agreed turns a single answer into an answer with a level of confidence, which is what a farmer needs to decide.

15Questions for the viva

The project carries a viva voce of its own, worth 5 marks. These are the questions it is most likely to ask about this project. Try answering each one aloud before opening it.

▸What are the features and the label in your project?

The features are nitrogen, phosphorus, potassium, pH, rainfall and temperature. The label is the crop.

▸Explain k nearest neighbours in simple words.

To recommend a crop for a new field, the model finds the k training fields most similar to it and recommends the crop that most of them grew.

▸Why did you rescale the features?

Rainfall is in hundreds and pH is under 10. Without rescaling, rainfall would decide the distance on its own just because its numbers are bigger.

▸Why did you choose k = 5?

With k = 1, one unusual field can mislead the model. The line chart showed that accuracy levels out from about three neighbours, so five is a safe choice.

▸Which crops did your model confuse, and why?

Maize and cotton, because their soil and weather are the most alike in the data.

▸Is your project supervised or unsupervised learning?

Supervised, because every field in the training data comes with the crop that grew well — the label.

▸Which SDG does it relate to?

SDG 2, Zero Hunger, because the right crop gives more food from the same land, and SDG 15, Life on Land, because it reduces wasted fertiliser.

16References

  • Soil Health Card scheme, Department of Agriculture and Farmers Welfare, Government of India — https://soilhealth.dac.gov.in/
  • India Meteorological Department — https://mausam.imd.gov.in/
  • Orange Data Mining, kNN widget — https://orangedatamining.com/widget-catalog/model/knn/
  • CBSE, Artificial Intelligence (Subject Code 417), Class X curriculum, 2026-27 — Part C, Practical and Project Work
  • CBSE, Artificial Intelligence Facilitator Handbook, Class X
  • pandas — https://pandas.pydata.org/docs/; NumPy — https://numpy.org/doc/; Matplotlib — https://matplotlib.org/stable/
  • LambdaLab — https://www.lambdalab.in
Key Takeaway
The PDF is the whole report — cover page, certificate, acknowledgement, index and every section on this page, ready to print. The cover, certificate and acknowledgement have blank lines where the names go: a certificate with somebody else's name printed on it is not a template. Fill those in, and build the project on data you collected yourself.