We ended the previous lesson noting that most of the systems NovaMarket is going to build are sub-symbolic: they learn from data. This lesson is devoted to that raw material. Marta has been saying it in every meeting for weeks: "before we talk about models, let's talk about what data we have and what state they are in". She is right, and in this lesson we will see why. We will explain why data are the fuel of AI, what types of data exist and how they are organised, what the life cycle of data is in an organisation, how their quality is measured and what concrete problems NovaMarket's files hide, where the biases come from that later turn into unfair decisions, what conceptual obligations personal-data protection imposes and why quality matters more than quantity. We will close with a Python program that reads a sample orders.csv and produces a small quality report, exactly the kind of check that should precede any AI project.
Contents
- Why data are the fuel of AI
- Types of data
- Labelled and unlabelled data
- The data life cycle: collection, storage, quality, governance
- Dimensions of data quality, with NovaMarket's real problems
- Bias in data: the origin of algorithmic unfairness
- Personal data and the GDPR: minimisation, anonymisation and pseudonymisation
- Quantity versus quality; synthetic data
- Example in Python: a quality report for
orders.csv
- Why data are the fuel of AI
Remember the scheme from 01-02: in traditional programming, data + program → results; in machine learning, data + results → model, and then the model produces results for new data. The model contains no more knowledge than there was in the data it was built from. Three consequences follow that are worth committing to memory:
- No data, no model. The recommender in 01-03 could not recommend anything for the TV until there were orders containing TVs (the "cold start").
- Bad data, bad models. The classic expression is garbage in, garbage out: if 50% of the "cause" column in
incidents.csvis empty, no algorithm is going to diagnose incidents well. - The model inherits everything in the data, including the errors, the gaps and the biases. If NovaMarket historically reviewed orders from certain postcodes more, a model trained on that history will learn to be suspicious of those postcodes (we will develop this in section 6 and in 02-04).
That is why, in a real AI project, between 60% and 80% of the effort goes into obtaining, understanding, cleaning and preparing data, not into training models. It is the first thing Marta wanted Diego to understand: the "cost of AI" is largely the cost of putting the data in order, and that cost is amortised across every subsequent project.
- Types of data
Data are classified in several complementary ways. Knowing them helps you know which technique can be applied and what preparation they will need.
2.1 By structure
| Type | What it is | Examples at NovaMarket | How it is worked with |
|---|---|---|---|
| Structured | Tables with rows and columns of fixed type | orders.csv, customers.csv, products.csv, the stock database |
SQL queries, spreadsheets, classical ML directly |
| Semi-structured | Has tags or hierarchy, but no rigid schema | JSON responses from the courier's API, web server log files, emails with headers | Fields must be extracted before analysis |
| Unstructured | No predefined organisation | Free text in reviews.csv, customer-service chat transcripts, photos of returned products, call recordings |
Require NLP, computer vision or prior extraction |
Most of a company's data volume is unstructured, and most of the historical value has been extracted from structured data. Deep learning (module 5) changed that by making text and images exploitable.
2.2 By the nature of the value
| Type | Description | Examples | Note |
|---|---|---|---|
| Numerical | Quantities it makes sense to operate on | Order amount, parcel weight, units sold | Can be continuous (amount) or discrete (units) |
| Categorical | Labels from a finite set | City, product category, order status, incident cause | Unordered (nominal: city) or ordered (ordinal: size S/M/L, satisfaction 1-5) |
| Text | Natural-language strings | Reviews, chat messages, product descriptions | Unstructured; requires NLP |
| Image / audio / video | Perceived signals | Product photos, photos of damage in returns, calls | Unstructured; requires vision or signal processing |
| Time series | Numerical values ordered in time | Daily sales per product, hourly web traffic, delivery times | Order matters; the basis of demand forecasting |
| Dates and identifiers | Timestamps, keys | date, order_id, customer_id |
Neither "really" numerical nor categorical: they serve to link and sort |
A single file mixes several: orders.csv has identifiers, a date, a numerical amount, a categorical city and a binary categorical returned column. Knowing what each column is is the first step of any analysis, and it is surprising how many problems come from treating an identifier as a number (adding up postcodes) or a number as text (sorting amounts alphabetically: "1000" before "200").
- Labelled and unlabelled data
A decisive distinction for machine learning:
- Labelled data: each example comes with the correct answer we want the model to learn to produce. In
orders.csv, thereturnedcolumn is the label if we want to predict returns. Inreviews.csv, a "sentiment" column (positive/negative) filled in by a person would be the label. - Unlabelled data: we only have the examples, with no answer. Thousands of unclassified reviews, browsing history without knowing what the customer ended up buying.
With labelled data you can do supervised learning (learning the relationship between the features and the label); with unlabelled data, unsupervised learning (discovering structure: groups of similar customers, products bought together). Both are developed in 04-02; here it is enough to know that labels are expensive (they often have to be applied by hand) and valuable, and that their quality conditions everything.
An example of how expensive and delicate a label is: incidents.csv has around 400 records and half of the cause column empty. That column is the label that use case 9 (incident diagnosis) would need to learn. With 200 examples labelled unevenly (operators filled in the cause when they had time, that is, more on quiet days and less at peaks), the system would learn from a biased sample. Diego proposed a non-technical but correct solution: make the cause mandatory on the form from now on and organise a session to label the 200 missing ones retrospectively.
- The data life cycle: collection, storage, quality, governance
Data do not appear ready to use; they go through a cycle, and each stage can introduce or correct problems:
flowchart LR
R[Collection<br/>forms, sensors,<br/>logs, APIs, purchases] --> A[Storage<br/>databases, files,<br/>data warehouse]
A --> C[Quality<br/>validation, cleaning,<br/>deduplication]
C --> U[Use<br/>analysis, models,<br/>decisions]
U --> G[Governance<br/>ownership, access,<br/>retention, compliance]
G -.-> R
- Collection: how the data come in. Each channel has its vices: the web returns form allows fields to be left empty; the warehouse terminal stores weight in grams and the website in kilos; the external courier sends dates in
DD/MM/YYYYformat while the internal system usesYYYY-MM-DD. The golden rule is validate at source: it is far cheaper to require the incident cause to be filled in at the time than to reconstruct it months later. - Storage: where and how they are kept. Transactional databases (the ones that run the store), data warehouses for analysis, flat files like the CSVs we work with in the course. Here traceability matters (where does each datum come from? when was it loaded?) as does the schema (what does each column mean, in what units).
- Quality: checking and correcting (section 5). Ideally it is a continuous process, not a one-off fix before each project.
- Use: analysis, reports, model training, decisions. It is what justifies everything above.
- Governance: the set of rules and responsibilities over the data: who owns each dataset (Marta proposed that each area own its own: operations owns
incidents.csv, marketing ownsreviews.csv), who can access what, how long data are retained, how they are documented (a data dictionary with the definition of each column) and how the legislation is complied with (section 7). Governance closes the cycle because its rules improve the next collection.
Without governance, every AI project rediscovers the same problems. With it, NovaMarket's second project (demand forecasting) reuses the clean data from the first (recommendation).
- Dimensions of data quality, with NovaMarket's real problems
"Quality data" is not a vague judgement: it breaks down into measurable dimensions. The five most used, with the problems Marta found when she opened NovaMarket's CSVs for the first time:
| Dimension | Question | Real problem found at NovaMarket | Consequence if not corrected | Possible fix |
|---|---|---|---|---|
| Completeness | Are values missing? | Half of the cause column in incidents.csv is empty; some orders without a postcode |
Incident diagnosis cannot learn; models that use the postcode discard rows | Make the field mandatory; label retrospectively; for the rest, decide whether to impute or discard (04-03) |
| Accuracy | Are the values correct? | Prices in products.csv in cents in part of the catalogue (imported from a supplier) and in euros in the rest: a cable at "1299" |
A forecasting model learns that a cable is worth more than a laptop; averages shoot up | Unify the unit; detect out-of-range values with rules |
| Consistency | Are the same facts represented the same way everywhere? | Mixed dates: 2026-03-02, 02/03/2026, 03-03-2026; city as "Zaragoza", "zaragoza", "ZARAGOZA " |
Sorting by date fails; "Zaragoza" and "zaragoza" count as different cities | Normalise formats and case on loading; validate at source |
| Timeliness | Do the data reflect the present state? | Customer addresses that are not updated; Getafe warehouse stock lagging by hours | Badly planned routes; orders assigned to warehouses without stock | Define the acceptable update frequency per datum; record the last-modified date |
| Uniqueness | Are there duplicates? | Duplicate customers in customers.csv (same email with different case or a trailing space; same person with two emails); the same order loaded twice |
The recommender sees two customers where there is one; sales are over-counted | Define the uniqueness key; deduplicate with normalisation; prevent double loading |
Two important remarks:
- Quality is measured, not assumed. Marta did what we will do in section 9: count blanks, duplicates and formats per column and put numbers on them. Discovering that 58% of the rows in a sample had some problem was what convinced Diego to devote time to cleaning before models.
- Quality depends on the use. An empty postcode is irrelevant for classifying reviews and serious for planning routes. There is no such thing as "the" quality level: there is enough for each use case.
How these problems are cleaned up to prepare a model (value imputation, category encoding, scaling, handling outliers) is the content of 04-03; here we stop at detecting and measuring them.
- Bias in data: the origin of algorithmic unfairness
A bias in the data is a systematic deviation between what the data represent and the reality they should represent. It is not a problem of "dirty data" (the data can be spotless and biased) but of what was collected, from whom and how it was labelled. Since the model inherits what is in the data, the bias turns into systematically skewed decisions, and when those decisions affect people, into algorithmic unfairness. The three main origins:
| Type of bias | What happens | Example at NovaMarket | Effect on the model |
|---|---|---|---|
| Sampling | The sample does not represent the population: some groups are over-represented and others almost absent | Reviews are written mostly by very satisfied or very angry customers; customers in cities without own delivery barely appear in incidents.csv because their incidents are handled by the courier |
The review classifier does not recognise lukewarm opinions; incident diagnosis knows nothing about courier cities |
| Historical | The data faithfully reflect a past that was unfair or different | For years the team manually reviewed orders from certain postcodes more; in the history those postcodes have more "fraud detected" simply because more searching was done there | The risk model learns that those postcodes are risky, and by reviewing more there it finds more, reinforcing the bias (feedback loop) |
| Labelling | The labels are applied by people (or processes) with inconsistent criteria or prejudices | The cause of incidents is filled in by different operators: some put "warehouse error" where others put "damaged product"; on peak days labelling is worse |
The model learns each operator's criterion, not the real cause |
Other frequent biases: survivorship bias (we only see the data that "survived": we analyse the customers who keep buying and forget why those who left did so), measurement bias (a sensor or form captures one group worse: the returns form in the app is more awkward than on the website, so mobile customers fill in less) and confirmation bias in the analysis (looking in the data for what we already believed, like Diego's €300 rule).
The essential point in this lesson: bias enters through the data and shows up in the decisions. How to assess its ethical consequences, how to measure the disparity between groups and what to do about it is the content of the next lesson, 02-04.
- Personal data and the GDPR: minimisation, anonymisation and pseudonymisation
A good part of NovaMarket's data are personal data: they identify or make it possible to identify a person (name, email, address, phone, purchase history, even a combination of postcode, date of birth and sex). In the European Union their processing is regulated by the General Data Protection Regulation (GDPR). We are not going to study the regulation here (it is picked up again in the regulatory framework of 02-04) but rather the principles that directly affect working with data for AI:
- Purpose and legal basis: data are collected for a specific purpose and with a legal basis (contract, consent, legitimate interest...). Using purchase history to recommend products may fit; using it for something unforeseen (selling it, profiling for third-party purposes) does not.
- Minimisation: collect and use only the data necessary for the purpose. If the date, the product, the city and the units are enough to forecast demand, there is no need to load the customer's name and email into the training set. Fewer personal data means less risk and fewer obligations.
- Storage limitation: do not keep personal data longer than necessary. This clashes with the instinct to "keep everything in case it is useful for training something"; retention periods must be defined.
- Individuals' rights: access, rectification, erasure ("to be forgotten"), objection to profiling and to automated decisions with significant effects. A model trained on the data of a customer who requests erasure raises practical questions the project must anticipate.
- Security: protecting the data from improper access, especially when they are copied to analysis environments or sent to cloud services (remember the cloud/device dichotomy from 02-02).
Two techniques that reduce risk and will come up constantly:
| Technique | What it does | Example | Is it still personal data? |
|---|---|---|---|
| Pseudonymisation | Replaces direct identifiers with a code; the mapping is kept separately and protected | customers.csv with customer_id = C1001 instead of the name and email; the table linking C1001 to the person is held by another team |
Yes (it can be reversed with the table), but with lower risk; the GDPR recognises it as a security measure |
| Anonymisation | Removes or transforms the data so that it is not possible to re-identify the person, even by combining them with others | Aggregating sales by city and week with no identifier; generalising the postcode to its first two digits; removing unique fields | No, if the anonymisation is real. But it is hard to achieve: the combination of a few "innocent" data can re-identify |
Marta decided that the datasets used to train models at NovaMarket will always be, at a minimum, pseudonymised, and that demand forecasting will work with aggregated (anonymous) data.
Important notice: everything above is a conceptual description for training purposes, not legal advice. Any project that processes personal data must be reviewed with the data protection officer or a legal professional, who will assess the legal basis, the need for an impact assessment and the specific measures.
- Quantity versus quality; synthetic data
It is common to hear that "the more data, the better". It is half true:
- More data help when they are varied and representative: they allow finer patterns to be learned and reduce the danger of the model memorising specific cases (overfitting, the subject of 04-06). The great advances of deep learning rested on enormous datasets.
- More bad data do not help: multiplying a biased set by ten gives a model ten times more confident in its bias. Two hundred well-labelled examples of incident causes are worth more than two thousand labelled any old how.
- The data must be relevant: for NovaMarket's recommender, the orders of another store in another sector contribute little.
Practical rule: first quality and representativeness; then quantity. And always measure whether more data actually improve the result.
Synthetic data are artificially generated data (by rules, simulation or generative models) that imitate the statistical properties of real ones. They are used to: fill out rare classes (there are very few real frauds to learn from), test systems without exposing personal data, or simulate scenarios that have not yet happened (a Christmas campaign for a new product). They have an obvious limit: they only contain what whoever generated them knew or assumed; they discover nothing new about the world, and if they are generated from biased data, they inherit the bias. They are a complement, not a substitute.
- Example in Python: a quality report for
orders.csv
orders.csvWe are going to do what Marta did on the first day: read an extract of orders.csv, count the problems per dimension and produce a small report. To keep the example self-contained, the CSV is written inside the program as a text string; in practice you would read it with open("orders.csv"). We only use the standard library: csv, io, re and collections.
9.1 The data and reading them
import csv
import io
import re
from collections import Counter
ORDERS_DATA = """order_id,customer_id,date,amount,city,postcode,returned
48201,C1001,2026-03-02,89.90,Zaragoza,50001,no
48202,C1002,02/03/2026,15990,Madrid,28045,no
48203,C1003,2026-03-02,,Getafe,28901,yes
48204,C1001,2026-03-03,45.00,zaragoza,50001,no
48205,C1004,03-03-2026,320.50,Valencia,46001,yes
48206,C1005,2026-03-03,12.99,Madrid,,no
48207,C1002,2026-03-04,159.90,Madrid,28045,no
48202,C1002,02/03/2026,15990,Madrid,28045,no
48208,C1006,2026-13-04,75.00,Sevilla,41001,no
48209,,2026-03-04,210.00,Bilbao,48001,
48210,C1007,2026-03-05,58.40,Zaragoza,50002,no
48211,C1008,2026-03-05,1299.00,Getafe,28901,no
"""
def read_orders(csv_text):
# io.StringIO turns the string into something that reads like a file.
# csv.DictReader returns each row as a dictionary {column: value}.
reader = csv.DictReader(io.StringIO(csv_text))
return list(reader)
orders = read_orders(ORDERS_DATA)
print(f"Rows read: {len(orders)}")
print(orders[0])Output:
Rows read: 12
{'order_id': '48201', 'customer_id': 'C1001', 'date': '2026-03-02', 'amount': '89.90', 'city': 'Zaragoza', 'postcode': '50001', 'returned': 'no'}Explanation: csv.DictReader reads the first line as the header and turns each following line into a dictionary, which lets us access values by name (row["amount"]). Notice that everything is read as text: '89.90' is a string, not a number; converting it will be our responsibility. If you look at the data carefully you will already see the planted problems: an empty amount, an empty customer_id, an empty returned, an empty postcode, three date formats, a date with month 13, an amount of 15990 that smells of cents, "zaragoza" in lowercase and order 48202 twice.
9.2 Completeness: counting blanks per column
def completeness_report(rows):
columns = rows[0].keys()
total = len(rows)
print(f"{'column':15s} {'missing':>7s} {'% missing':>9s}")
for col in columns:
missing = sum(1 for r in rows if r[col].strip() == "")
print(f"{col:15s} {missing:7d} {100*missing/total:8.1f}%")
completeness_report(orders)Output:
column missing % missing order_id 0 0.0% customer_id 1 8.3% date 0 0.0% amount 1 8.3% city 0 0.0% postcode 1 8.3% returned 1 8.3%
Explanation: for each column we walk through the rows and count those whose cell is empty after stripping whitespace (strip()); a blank space is also a blank. The expression sum(1 for r in rows if ...) is a compact way of counting. With the real incidents.csv, this report is the one that would show "cause: 50.0%".
9.3 Uniqueness: duplicates
def detect_duplicates(rows, key):
# Counter counts how many times each value of the key appears.
counter = Counter(r[key] for r in rows)
return {value: times for value, times in counter.items() if times > 1}
print(detect_duplicates(orders, "order_id")) # {'48202': 2}Explanation: Counter builds a dictionary value → number of occurrences; we keep those that appear more than once. Here the duplicate is exact (the same row loaded twice), the easy case. The hard duplicates are those in customers.csv: the same customer with the email in upper case or with a trailing space. For those, the key would have to be normalised before counting: r["email"].strip().lower(). Try it as a variant.
9.4 Consistency: mixed date formats
DATE_PATTERNS = {
"YYYY-MM-DD": re.compile(r"^\d{4}-\d{2}-\d{2}$"),
"DD/MM/YYYY": re.compile(r"^\d{2}/\d{2}/\d{4}$"),
"DD-MM-YYYY": re.compile(r"^\d{2}-\d{2}-\d{4}$"),
}
def classify_date(text):
for name, pattern in DATE_PATTERNS.items():
if pattern.match(text):
return name
return "unknown"
formats = Counter(classify_date(r["date"]) for r in orders)
print(formats) # Counter({'YYYY-MM-DD': 9, 'DD/MM/YYYY': 2, 'DD-MM-YYYY': 1})Explanation: each pattern is a regular expression: ^\d{4}-\d{2}-\d{2}$ means "exactly four digits, hyphen, two digits, hyphen, two digits". classify_date returns the name of the first pattern that matches. The final Counter tells us how many dates there are in each format: three rows do not follow the standard. Sorting or comparing those dates as text would give absurd results, and any program expecting YYYY-MM-DD would fail on them.
9.5 Accuracy: impossible values and suspicious units
def valid_date(text):
# We only check the ones that already have the standard format.
if not DATE_PATTERNS["YYYY-MM-DD"].match(text):
return False
year, month, day = (int(x) for x in text.split("-"))
return 1 <= month <= 12 and 1 <= day <= 31
impossible = [r["order_id"] for r in orders
if classify_date(r["date"]) == "YYYY-MM-DD" and not valid_date(r["date"])]
print("Impossible dates:", impossible) # ['48208'] (month 13)
def suspicious_amounts(rows, threshold=5000):
suspicious = []
for r in rows:
if r["amount"].strip() == "":
continue # blanks are already counted under completeness
value = float(r["amount"])
if value > threshold:
suspicious.append((r["order_id"], value))
return suspicious
print("Suspicious amounts:", suspicious_amounts(orders))
# [('48202', 15990.0), ('48202', 15990.0)]Explanation: accuracy cannot be fully checked without a source of truth, but we can detect impossible values (month 13) and values outside the reasonable range (a €15,990 order in a store whose average basket is around €100 smells of an amount in cents: €159.90). The €5,000 threshold is a business rule Diego set. Notice that the suspicious amount appears twice because the row is duplicated: quality problems pile up.
9.6 Consistency of categories
def cities_report(rows):
as_is = Counter(r["city"] for r in rows)
normalised = Counter(r["city"].strip().lower() for r in rows)
print("Distinct cities as is:", len(as_is), sorted(as_is))
print("Distinct cities normalised:", len(normalised))
cities_report(orders)Output:
Distinct cities as is: 7 ['Bilbao', 'Getafe', 'Madrid', 'Sevilla', 'Valencia', 'Zaragoza', 'zaragoza'] Distinct cities normalised: 6
Explanation: counting the values as they are there are seven cities; normalising (no whitespace, lowercase) there are six. The difference is exactly the number of inconsistencies. This trick (comparing the number of distinct values before and after normalising) works for any categorical column.
9.7 The complete quality report
def quality_report(rows):
total = len(rows)
print("=== QUALITY REPORT: orders.csv ===")
print(f"Records: {total}")
dup = detect_duplicates(rows, "order_id")
print(f"Uniqueness: {len(dup)} repeated order_id {sorted(dup)}")
missing = sum(1 for r in rows for col in r if r[col].strip() == "")
print(f"Completeness: {missing} empty cells")
formats = Counter(classify_date(r["date"]) for r in rows)
non_standard = total - formats.get("YYYY-MM-DD", 0)
print(f"Consistency: {non_standard} dates outside YYYY-MM-DD")
impossible = [r["order_id"] for r in rows
if classify_date(r["date"]) == "YYYY-MM-DD" and not valid_date(r["date"])]
susp = suspicious_amounts(rows)
susp_ids = {s[0] for s in susp}
print(f"Accuracy: {len(impossible)} impossible dates, {len(susp)} suspicious amounts")
# A row is problematic if it has at least one problem of any kind
rows_with_issues = 0
for r in rows:
issue = (any(r[c].strip() == "" for c in r)
or dup.get(r["order_id"], 0) > 1
or classify_date(r["date"]) != "YYYY-MM-DD"
or r["order_id"] in impossible
or r["order_id"] in susp_ids)
if issue:
rows_with_issues += 1
percentage = 100 * rows_with_issues / total
print(f"Rows with at least one issue: {rows_with_issues} of {total} ({percentage:.0f}%)")
print(f"Quality score: {100 - percentage:.0f}/100")
quality_report(orders)Output:
=== QUALITY REPORT: orders.csv === Records: 12 Uniqueness: 1 repeated order_id ['48202'] Completeness: 4 empty cells Consistency: 3 dates outside YYYY-MM-DD Accuracy: 1 impossible dates, 2 suspicious amounts Rows with at least one issue: 7 of 12 (58%) Quality score: 42/100
Explanation: the report brings together the previous checks and adds a global metric: the percentage of rows with at least one problem. 58% problematic rows in a small, exaggerated sample; in NovaMarket's real file the figure was lower but enough to justify a preliminary cleaning project. The "quality score" is a simplification (all dimensions weigh the same), but it turns a feeling into a number that can be tracked over time: Marta's goal is for it to rise month by month. As a mental exercise, think about what you would add to measure timeliness (you would need a column with the last-updated date) and what you would need to measure accuracy for real (a reference source to compare against).
Common Mistakes and Tips
- Starting with the model. The natural impulse is to train something as soon as possible. Always start with a quality report like the one in section 9; an hour of checks saves weeks of debugging why the model "does strange things".
- Trusting that a clean file is not biased. Quality and representativeness are different things. Always ask who is missing from the data and why.
- Treating a blank as zero, or as just another category, without thinking. An empty amount is not an amount of €0; an empty cause is not "no cause". Before deciding what to do with blanks you have to understand why they are missing (is it random, or is more missing on peak days?).
- Ignoring units. Cents versus euros, grams versus kilos, local time versus UTC. Document the units in the data dictionary and validate the ranges.
- Keeping everything "just in case". It clashes with the GDPR's minimisation and storage limitation and increases risk. Collect and keep what is necessary for a defined purpose.
- Anonymising by removing only the name. Postcode + date of birth + sex identify a large part of the population. If you need to anonymise for real, aggregate or generalise, and ask for expert review.
- Tip: create the data dictionary from day one (column, meaning, type, unit, allowed values, who is responsible). It is the most useful and least glamorous document of an AI project.
Exercises
Exercise 1: Classifying NovaMarket's data
For each of the five files (customers.csv, orders.csv, products.csv, reviews.csv, incidents.csv), state: (a) whether it is structured, semi-structured or unstructured (or mixed: which columns of each type); (b) which column could be a label and for which use case from the list in 01-03; (c) whether it contains personal data and which measure (pseudonymisation, anonymisation, minimisation) you would apply before using it for training.
Exercise 2: Bias in the return-risk model
NovaMarket wants to train a model that predicts whether an order will be returned, using the history of orders.csv from the last three years. During that period: (i) returns from the mobile app were recorded in another system and are not in the file; (ii) Diego's team manually reviewed and cancelled many "doubtful" returns on orders over €300 (his old rule); (iii) the return_reason column was filled in freely by customers. Identify which type of bias each circumstance introduces and what effect it would have on the model.
Exercise 3: Extending the quality report
Extend the program in section 9 with two checks: (a) the returned field only admits the values yes and no (anything else, including blank, is a consistency problem); (b) the postcode, when not empty, must have exactly 5 digits and its first two digits must be consistent with the city for at least these cases: Zaragoza → 50, Madrid → 28, Getafe → 28 (an accuracy problem). Add the count of both to the report.
Solutions
Solution 1.
| File | Structure | Possible label (use case) | Personal data and measure |
|---|---|---|---|
customers.csv |
Structured (name, email, address, sign-up date) | None obvious; "active/inactive customer" could be added to predict churn (not in the list of 9, but common) | Yes, fully: pseudonymise (replace name and email with customer_id); minimise (is the full address needed to recommend? the city is probably enough) |
orders.csv |
Structured | returned (case 3, return risk); units per product and date are the time series for case 2 (forecasting) |
Yes, as long as it contains a linkable customer_id: pseudonymised; for demand forecasting, aggregate by product/city/day (anonymised) |
products.csv |
Structured (category, price, weight) with one unstructured column (text description) | None; it is context information (features) for cases 1, 2 and 6 | Contains no personal data |
reviews.csv |
Mixed: structured columns (product_id, score, date) and unstructured free text |
score (1-5) can serve as an approximate sentiment label for case 4; a hand-applied label is better |
Yes: customer_id and, potentially, the text itself (people write their name or details); pseudonymise and review the text |
incidents.csv |
Mixed: structured columns (order_id, date, type, cause) and free description |
cause (case 9, diagnosis), with the problem of being empty in half the rows |
Yes, indirectly via order_id → customer; pseudonymise |
Solution 2.
- (i) App returns missing: sampling bias (or measurement bias). The model will underestimate returns from customers who buy on mobile; if the channel is correlated with age or product type, the model will be systematically wrong for those groups.
- (ii) Manual cancellations of doubtful returns over €300: historical bias (the data reflect a past policy). The history contains fewer "completed" returns on high-value orders than would occur without Diego's intervention; the model may learn that expensive orders are rarely returned, exactly the opposite of what motivated the rule, and it also absorbs the subjective criterion of what was "doubtful". If the model is used to decide what to review, a feedback loop is created.
- (iii) Reason written freely by the customer: labelling bias (and a consistency problem). "I don't like it", "it wasn't what I expected", "size" and "poor quality" may or may not be the same thing; the model learns the words each customer uses, not the real cause. The reasons would have to be normalised to a closed list before using them as a label.
Solution 3.
def returned_issues(rows):
return [r["order_id"] for r in rows if r["returned"].strip() not in ("yes", "no")]
POSTCODE_PREFIXES = {"zaragoza": "50", "madrid": "28", "getafe": "28"}
def postcode_issues(rows):
bad = []
for r in rows:
pc = r["postcode"].strip()
if pc == "":
continue # already counted under completeness
city = r["city"].strip().lower()
if not re.fullmatch(r"\d{5}", pc):
bad.append((r["order_id"], pc, "format"))
elif city in POSTCODE_PREFIXES and not pc.startswith(POSTCODE_PREFIXES[city]):
bad.append((r["order_id"], pc, "does not match " + r["city"]))
return bad
print("invalid returned:", returned_issues(orders)) # ['48209']
print("postcode:", postcode_issues(orders)) # [] with the sample dataTo incorporate it into the report, add two lines inside quality_report with len(returned_issues(rows)) and len(postcode_issues(rows)) and include those identifiers in the "row with issue" condition. With the sample data the postcode check finds no errors; add a row 48212,C1009,2026-03-06,20.00,Madrid,50003,no and check that it detects it.
Conclusion
In this lesson we have treated data as what they are in modern AI: the raw material from which the model inherits everything, the knowledge as well as the errors and biases. We have classified data by structure (structured, semi-structured, unstructured), by nature (numerical, categorical, text, image, time series) and by the presence of labels, anticipating the distinction between supervised and unsupervised learning. We have gone through the data life cycle (collection, storage, quality, use, governance) and broken quality down into five measurable dimensions (completeness, accuracy, consistency, timeliness, uniqueness) illustrated with the real problems of NovaMarket's CSVs: half of the cause column empty, duplicate customers, dates in three formats and prices in cents. We have seen that bias enters through the data (sampling, historical, labelling) and turns into skewed decisions, we have presented the GDPR principles that affect working with data (purpose, minimisation, retention, rights, security) together with pseudonymisation and anonymisation, and we have qualified the myth that "more data is always better". Finally, we have written a program that reads a CSV with the standard library and produces a quality report: the first tool that should be run in any AI project.
With the data now on the table, the next lesson, Ethics and Considerations in AI, tackles what happens when those data, with their biases, feed decisions that affect people: which ethical principles should guide NovaMarket, which concrete problems (discrimination, privacy, opacity, accountability, impact on jobs, disinformation, sustainability) must be anticipated, what the European regulatory framework requires and which practical tools (checklist, impact assessment, human review, fairness metrics) make it possible to move from good intentions to facts. There we will pick up the return-risk model and the postcode again to measure, with numbers, whether a model treats all groups the same.
Fundamentals of Artificial Intelligence (AI)
Module 1: Introduction to Artificial Intelligence
Module 2: Basic Principles of AI
- Fundamental Concepts: Agents, Environments and Rationality
- Types of Artificial Intelligence
- Data as the Raw Material of AI
- Ethics and Considerations in AI
Module 3: Algorithms in AI
- Introduction to Algorithms
- Search Algorithms
- Adversarial Search: Games and Minimax
- Optimization Algorithms
Module 4: Machine Learning
- Basic Concepts of Machine Learning
- Types of Machine Learning
- Data Preparation and Feature Engineering
- Machine Learning Algorithms
- Model Evaluation and Validation
- Overfitting, Regularization and Hyperparameter Tuning
Module 5: Neural Networks and Deep Learning
- Introduction to Neural Networks
- Neural Network Architecture
- How a Network Learns: Gradient Descent and Backpropagation
- Deep Learning and Its Applications
- Transformers, Large Language Models and Generative AI
Module 6: Logic and Expert Systems
- Logic in AI
- Expert Systems
- Reasoning under Uncertainty: Probability and Bayesian Networks
- Applications of Expert Systems
Module 7: Tools and Programming Languages in AI
- Programming Languages for AI
- Scientific Python: NumPy, pandas and Matplotlib
- Popular Tools and Libraries
- Development Environments
Module 8: Projects and Case Studies
Module 9: Exercises and Practice
- Algorithm Exercises
- Machine Learning Practice
- Neural Network Projects
- Capstone Project: from Idea to Prototype
