Learning by Doing — Python Topic #2 Aprender Haciendo — Python Tema #2

Clean Data in Minutes with Python Limpia Datos en Minutos con Python

A practical, recording-ready guide for cleaning a messy CSV file locally with Python, Jupyter Notebook, and pandas. No empty theory. Upload the file, inspect the problem, clean it step by step, validate the result, and export a clean dataset.

Una guía práctica, lista para grabar, para limpiar un CSV desordenado localmente con Python, Jupyter Notebook y pandas. Nada de teoría vacía. Subes el archivo, inspeccionas el problema, limpias paso a paso, validas el resultado y exportas un dataset limpio.

Recorded practice: Learning by Doing — Python Topic #2: Clean Data in Minutes. Práctica grabada: Learning by Doing — Python Topic #2: Clean Data in Minutes.

Dataset note: This practice uses a synthetic dataset created for educational purposes. It does not use real data from the source video or from any organization. Nota sobre el dataset: Esta práctica usa un dataset sintético creado con fines educativos. No usa datos reales del video fuente ni de ninguna organización.
Cybersecurity and production warning: Do not run scripts on production systems, real organizational folders, confidential data, or shared drives without authorization. Always work on a copy, keep backups, test with sample data first, use least-privilege permissions, follow change-control rules, and comply with your organization’s cybersecurity protocols. Advertencia de ciberseguridad y ambiente de producción: No ejecutes scripts en sistemas de producción, carpetas reales de la organización, datos confidenciales o unidades compartidas sin autorización. Trabaja siempre sobre una copia, mantén backups, prueba primero con datos de muestra, usa permisos mínimos necesarios, sigue control de cambios y cumple los protocolos de ciberseguridad de tu organización.

Local workflow for this recording

Flujo local para esta grabación

In Topic #2 we are no longer depending on Jupyter Notebook local. We are working with Python installed locally, a virtual environment, and Jupyter Notebook running from the project folder. This is closer to a real work scenario: local folder, CSV file, notebook, cleaning logic, validation, and exported files.

En el Topic #2 ya no dependemos de Jupyter Notebook local. Estamos trabajando con Python instalado localmente, un ambiente virtual y Jupyter Notebook corriendo desde la carpeta del proyecto. Esto se parece más a un escenario real de trabajo: carpeta local, archivo CSV, notebook, lógica de limpieza, validación y archivos exportados.

What we will build

Qué vamos a construir

A complete mini-pipeline that takes a messy sales file and produces a clean CSV ready for analysis.

Un mini-pipeline completo que toma un archivo de ventas desordenado y produce un CSV limpio listo para análisis.

  • Create or load a CSV locally with Python and Jupyter Notebook.
  • Inspect columns, types, nulls, duplicates, and sample rows.
  • Standardize column names and text categories.
  • Convert currency and dates correctly.
  • Validate totals and detect impossible values.
  • Export the cleaned file.
  • Crear o cargar un CSV localmente con Python y Jupyter Notebook.
  • Inspeccionar columnas, tipos, nulos, duplicados y filas de muestra.
  • Estandarizar nombres de columnas y categorías de texto.
  • Convertir moneda y fechas correctamente.
  • Validar totales y detectar valores imposibles.
  • Exportar el archivo limpio.

Suggested recording flow

Flujo sugerido para grabar

The instructor asks for one script at a time, runs it live, checks the result, and moves to the next step. This keeps the session natural, practical, and collaborative.

El instructor pide un script a la vez, lo corre en vivo, revisa el resultado y pasa al siguiente paso. Así la sesión se mantiene natural, práctica y colaborativa.

  • Step 1: Create sample messy data.
  • Step 2: Save and load the CSV locally.
  • Step 3: Diagnose issues.
  • Step 4: Clean and standardize.
  • Step 5: Validate and export.
  • Paso 1: Crear data sintética desordenada.
  • Paso 2: Guardar y cargar el CSV localmente.
  • Paso 3: Diagnosticar problemas.
  • Paso 4: Limpiar y estandarizar.
  • Paso 5: Validar y exportar.

0Optional: create a messy practice CSVOpcional: crear un CSV desordenado de práctica

Use this first if you do not have a file yet. It creates a messy dataset with missing values, duplicated rows, inconsistent categories, currency symbols, and date issues.

Usa esto primero si todavía no tienes un archivo. Crea un dataset desordenado con valores faltantes, duplicados, categorías inconsistentes, símbolos de moneda y problemas de fecha.

Python — create sample data
import pandas as pd
import numpy as np

data = {
    "Order ID": [1001, 1002, 1003, 1003, 1004, 1005, 1006, 1007, 1008, 1009],
    "Company Name": ["Acme Inc", " acme inc ", "Global LLC", "GLOBAL LLC", "Nova Co", None, "River Corp", "River Corp", "Sun LLC", "Sun LLC"],
    "Product": ["Laptop", "laptop", "Monitor", "Monitor", "Keyboard", "Mouse", "Laptop", "Laptop", "Desk", "Desk"],
    "Order Date": ["01/05/2026", "2026-01-06", "bad date", "01/07/2026", "01/08/2026", "", "01/09/2026", "01/09/2026", "01/10/2026", "01/10/2026"],
    "Quantity": [2, 1, 3, 3, None, 5, 1, 1, -2, 2],
    "Unit Price": ["$1,200.00", "$1,200.00", "$300.00", "$300.00", "$75.50", "$25.00", "$1,150.00", "$1,150.00", "$450.00", "$450.00"],
    "Total": ["$2,400.00", "$1,200.00", "$900.00", "$900.00", "$75.50", "$125.00", "$1,150.00", "$999.00", "-$900.00", "$900.00"],
    "Paid": ["Yes", "Y", "No", "N", "yes", "no", "True", "False", "Y", None]
}

df_messy = pd.DataFrame(data)
df_messy.to_csv("messy_sales_data.csv", index=False)

print("messy_sales_data.csv created successfully.")
df_messy.head(10)

1Preparation and environmentPreparación y entorno

Import what we need and load the CSV file from the same local project folder where the notebook is running.

Importamos lo necesario y cargamos el CSV desde la misma carpeta local del proyecto donde está corriendo el notebook.

Python — imports and local file check
import pandas as pd
import numpy as np
from pathlib import Path

project_folder = Path.cwd()

print("Current project folder:")
print(project_folder)

print("\nFiles in this folder:")
for file in project_folder.iterdir():
    if file.is_file():
        print("-", file.name)

Note: If you created the sample file in Step 0, use the script below to load it locally.

Nota: Si creaste el archivo de muestra en el Paso 0, usa el script de abajo para cargarlo localmente.

Python — load local sample file
df = pd.read_csv("messy_sales_data.csv")
df.head()

2Initial inspection and diagnosisInspección y diagnóstico inicial

Before cleaning, we need to see the shape, columns, data types, missing values, duplicates, and a few sample rows.

Antes de limpiar, necesitamos ver tamaño, columnas, tipos de datos, valores faltantes, duplicados y algunas filas de muestra.

Python — quick diagnosis
print("Rows and columns:", df.shape)

print("\nColumn names:")
print(df.columns.tolist())

print("\nData types:")
print(df.dtypes)

print("\nMissing values per column:")
print(df.isnull().sum())

print("\nDuplicate rows:")
print(df.duplicated().sum())

df.head(10)

3Standardize column namesEstandarizar nombres de columnas

Clean column names make the rest of the script easier and safer.

Limpiar los nombres de columnas hace que el resto del script sea más fácil y seguro.

Python — clean column names
df.columns = (
    df.columns
    .str.strip()
    .str.lower()
    .str.replace(" ", "_", regex=False)
)

print(df.columns.tolist())
df.head()

4Analyze missing values and duplicatesAnalizar valores faltantes y duplicados

Percentages are more useful than raw counts because they show the relative quality of each column.

Los porcentajes son más útiles que los conteos porque muestran la calidad relativa de cada columna.

Python — null percentage and duplicate review
null_percent = df.isnull().mean().sort_values(ascending=False) * 100

print("Missing value percentage:")
print(null_percent.round(2))

duplicates = df[df.duplicated(keep=False)]

print("\nDuplicated records:")
display(duplicates)

5Clean text categoriesLimpiar categorías de texto

Text values often contain extra spaces, inconsistent capitalization, and different labels for the same meaning.

Los textos muchas veces tienen espacios extra, mayúsculas inconsistentes y etiquetas diferentes para el mismo significado.

Python — standardize text columns
text_columns = ["company_name", "product", "paid"]

for col in text_columns:
    if col in df.columns:
        df[col] = df[col].astype("string").str.strip().str.lower()

# Standardize company names and products
if "company_name" in df.columns:
    df["company_name"] = df["company_name"].replace({
        "acme inc": "Acme Inc",
        "global llc": "Global LLC",
        "nova co": "Nova Co",
        "river corp": "River Corp",
        "sun llc": "Sun LLC"
    })

if "product" in df.columns:
    df["product"] = df["product"].str.title()

# Standardize Yes/No style answers into True/False
if "paid" in df.columns:
    paid_map = {
        "yes": True,
        "y": True,
        "true": True,
        "no": False,
        "n": False,
        "false": False
    }
    df["paid"] = df["paid"].map(paid_map)

df.head(10)

6Convert currency fields into numbersConvertir campos de moneda a números

Python cannot calculate correctly with values like $1,200.00 until we remove symbols and convert the field to numeric.

Python no puede calcular correctamente valores como $1,200.00 hasta que quitamos símbolos y convertimos el campo a numérico.

Python — clean currency columns
currency_columns = ["unit_price", "total"]

for col in currency_columns:
    if col in df.columns:
        df[col] = (
            df[col]
            .astype("string")
            .str.replace("$", "", regex=False)
            .str.replace(",", "", regex=False)
            .str.replace("(", "-", regex=False)
            .str.replace(")", "", regex=False)
            .str.strip()
        )
        df[col] = pd.to_numeric(df[col], errors="coerce")

df[currency_columns].head()

7Convert dates and create time featuresConvertir fechas y crear variables de tiempo

Dates must be real datetime values before we can analyze trends by year, month, or day.

Las fechas deben ser valores datetime reales antes de analizar tendencias por año, mes o día.

Python — date conversion and features
if "order_date" in df.columns:
    df["order_date"] = pd.to_datetime(df["order_date"], errors="coerce")

    df["order_year"] = df["order_date"].dt.year
    df["order_month"] = df["order_date"].dt.month
    df["order_day"] = df["order_date"].dt.day

df[["order_date", "order_year", "order_month", "order_day"]].head(10)

8Handle missing valuesManejar valores faltantes

For numeric fields, median is often safer than average because it is less affected by extreme values. For categories, we can keep the row alive by using “Unknown”.

En campos numéricos, la mediana suele ser más segura que el promedio porque se afecta menos por valores extremos. En categorías, podemos conservar la fila usando “Unknown”.

Python — impute missing values
numeric_columns = ["quantity", "unit_price", "total"]

for col in numeric_columns:
    if col in df.columns:
        median_value = df[col].median()
        df[col] = df[col].fillna(median_value)

categorical_columns = ["company_name", "product"]

for col in categorical_columns:
    if col in df.columns:
        df[col] = df[col].fillna("Unknown")

if "paid" in df.columns:
    df["paid"] = df["paid"].fillna(False)

print(df.isnull().sum())
df.head(10)

9Remove duplicatesEliminar duplicados

Duplicated records may inflate sales, counts, and KPIs. Always review before deleting in real projects.

Los duplicados pueden inflar ventas, conteos y KPIs. En proyectos reales siempre se revisan antes de borrar.

Python — drop duplicated rows
before = len(df)

df = df.drop_duplicates()

after = len(df)

print("Rows before:", before)
print("Rows after:", after)
print("Duplicates removed:", before - after)

10Validate business logicValidar lógica de negocio

A clean dataset is not only formatted correctly. It must also make sense. Here we recalculate the total and flag mismatches.

Un dataset limpio no solo tiene formato correcto. También debe tener sentido. Aquí recalculamos el total y marcamos diferencias.

Python — validate totals
df["calculated_total"] = df["quantity"] * df["unit_price"]
df["total_difference"] = df["total"] - df["calculated_total"]

df["total_status"] = np.where(
    df["total_difference"].abs() <= 0.01,
    "OK",
    "Review"
)

display(df[["order_id", "quantity", "unit_price", "total", "calculated_total", "total_difference", "total_status"]])

11Detect impossible valuesDetectar valores imposibles

Some values require business review, not automatic deletion. Negative quantities, zero prices, and invalid dates are examples.

Algunos valores requieren revisión de negocio, no eliminación automática. Cantidades negativas, precios cero y fechas inválidas son ejemplos.

Python — impossible values report
issues = pd.DataFrame()

if "quantity" in df.columns:
    issues = pd.concat([issues, df[df["quantity"] <= 0]])

if "unit_price" in df.columns:
    issues = pd.concat([issues, df[df["unit_price"] <= 0]])

if "order_date" in df.columns:
    issues = pd.concat([issues, df[df["order_date"].isnull()]])

issues = issues.drop_duplicates()

print("Rows requiring review:", len(issues))
display(issues)

12Export clean data and review reportExportar datos limpios y reporte de revisión

Export the clean file and a separate review file for records that should be checked manually.

Exportamos el archivo limpio y un archivo separado de revisión para registros que deben verificarse manualmente.

Python — export files locally
df.to_csv("clean_sales_data.csv", index=False)
issues.to_csv("rows_to_review.csv", index=False)

print("Files created in the current local project folder:")
print("- clean_sales_data.csv")
print("- rows_to_review.csv")

Complete script — one block version

Script completo — versión en un solo bloque

Use this after practicing step by step. It runs the entire cleaning process from local CSV load to export.

Usa esto después de practicar paso a paso. Ejecuta todo el proceso de limpieza desde la carga local del CSV hasta la exportación.

Python — full cleaning pipeline
import pandas as pd
import numpy as np
from pathlib import Path

# 1. Load local CSV file from the current project folder
file_name = "messy_sales_data.csv"
df = pd.read_csv(file_name)

print("File loaded:", Path(file_name).resolve())

# 2. Standardize column names
df.columns = (
    df.columns
    .str.strip()
    .str.lower()
    .str.replace(" ", "_", regex=False)
)

# 3. Standardize text columns
for col in ["company_name", "product", "paid"]:
    if col in df.columns:
        df[col] = df[col].astype("string").str.strip().str.lower()

if "company_name" in df.columns:
    df["company_name"] = df["company_name"].replace({
        "acme inc": "Acme Inc",
        "global llc": "Global LLC",
        "nova co": "Nova Co",
        "river corp": "River Corp",
        "sun llc": "Sun LLC"
    })

if "product" in df.columns:
    df["product"] = df["product"].str.title()

if "paid" in df.columns:
    paid_map = {
        "yes": True, "y": True, "true": True,
        "no": False, "n": False, "false": False
    }
    df["paid"] = df["paid"].map(paid_map)

# 4. Clean currency columns
for col in ["unit_price", "total"]:
    if col in df.columns:
        df[col] = (
            df[col]
            .astype("string")
            .str.replace("$", "", regex=False)
            .str.replace(",", "", regex=False)
            .str.replace("(", "-", regex=False)
            .str.replace(")", "", regex=False)
            .str.strip()
        )
        df[col] = pd.to_numeric(df[col], errors="coerce")

# 5. Convert quantity to numeric
if "quantity" in df.columns:
    df["quantity"] = pd.to_numeric(df["quantity"], errors="coerce")

# 6. Convert dates
if "order_date" in df.columns:
    df["order_date"] = pd.to_datetime(df["order_date"], errors="coerce")
    df["order_year"] = df["order_date"].dt.year
    df["order_month"] = df["order_date"].dt.month
    df["order_day"] = df["order_date"].dt.day

# 7. Handle missing values
for col in ["quantity", "unit_price", "total"]:
    if col in df.columns:
        df[col] = df[col].fillna(df[col].median())

for col in ["company_name", "product"]:
    if col in df.columns:
        df[col] = df[col].fillna("Unknown")

if "paid" in df.columns:
    df["paid"] = df["paid"].fillna(False)

# 8. Remove duplicates
df = df.drop_duplicates()

# 9. Validate totals
if {"quantity", "unit_price", "total"}.issubset(df.columns):
    df["calculated_total"] = df["quantity"] * df["unit_price"]
    df["total_difference"] = df["total"] - df["calculated_total"]
    df["total_status"] = np.where(
        df["total_difference"].abs() <= 0.01,
        "OK",
        "Review"
    )

# 10. Create review report
issues = pd.DataFrame()

if "quantity" in df.columns:
    issues = pd.concat([issues, df[df["quantity"] <= 0]])

if "unit_price" in df.columns:
    issues = pd.concat([issues, df[df["unit_price"] <= 0]])

if "order_date" in df.columns:
    issues = pd.concat([issues, df[df["order_date"].isnull()]])

if "total_status" in df.columns:
    issues = pd.concat([issues, df[df["total_status"] == "Review"]])

issues = issues.drop_duplicates()

# 11. Export locally
df.to_csv("clean_sales_data.csv", index=False)
issues.to_csv("rows_to_review.csv", index=False)

print("Cleaning completed.")
print("Clean rows:", len(df))
print("Rows to review:", len(issues))
print("Files created in:", Path.cwd())

14Validate the files createdValidación de los ficheros creados

After exporting the results, we validate the local project folder. The goal is to confirm that the notebook, the messy CSV, the clean CSV, and the review file were all created successfully.

Después de exportar los resultados, validamos la carpeta local del proyecto. El objetivo es confirmar que el notebook, el CSV desordenado, el CSV limpio y el archivo de revisión fueron creados correctamente.

Python — validate created files
# Validar los ficheros creados en la carpeta local

from pathlib import Path

project_folder = Path.cwd()

expected_files = [
    "messy_sales_data.csv",
    "clean_sales_data.csv",
    "rows_to_review.csv",
    "Python_Topic_2_Clean_Data.ipynb"
]

print("Project folder:")
print(project_folder)

print("\nFile validation:")
for file_name in expected_files:
    file_path = project_folder / file_name
    status = "FOUND" if file_path.exists() else "MISSING"
    print(f"{file_name}: {status}")
Local project folder showing created Python, CSV, notebook, and review files
Final local folder validation: virtual environment, notebook, messy CSV, clean CSV, and rows-to-review CSV created successfully.
Validación final de la carpeta local: ambiente virtual, notebook, CSV desordenado, CSV limpio y CSV de filas para revisión creados correctamente.

15Calculate the average salesCalcular el promedio de ventas

After cleaning the data, the analytics part becomes simple. Here we calculate the average value of the total column.

Después de limpiar los datos, la parte analítica se vuelve simple. Aquí calculamos el promedio de la columna total.

Python — average sales
# Promedio de ventas de toda la data

average_sales = df["total"].mean()

print("Average sales:")
print(f"${average_sales:,.2f}")
Python — basic sales summary
# Resumen general de ventas

total_sales = df["total"].sum()
average_sales = df["total"].mean()
min_sales = df["total"].min()
max_sales = df["total"].max()
record_count = len(df)

print("Sales summary")
print("----------------")
print(f"Rows: {record_count}")
print(f"Total sales: ${total_sales:,.2f}")
print(f"Average sales: ${average_sales:,.2f}")
print(f"Minimum sale: ${min_sales:,.2f}")
print(f"Maximum sale: ${max_sales:,.2f}")

16Calculate the standard deviation of salesCalcular la desviación estándar de ventas

The average gives us a typical value. The standard deviation tells us how much the sales values move around that average.

El promedio nos da un valor típico. La desviación estándar nos dice cuánto se mueven los valores de ventas alrededor de ese promedio.

Python — sales standard deviation
# Desviación estándar de las ventas

std_sales = df["total"].std()

print("Sales standard deviation:")
print(f"${std_sales:,.2f}")
Python — average and standard deviation
# Promedio y desviación estándar de ventas

average_sales = df["total"].mean()
std_sales = df["total"].std()

print("Sales variability summary")
print("-------------------------")
print(f"Average sales: ${average_sales:,.2f}")
print(f"Standard deviation: ${std_sales:,.2f}")

17Calculate the coefficient of variationCalcular el coeficiente de variación

The coefficient of variation divides the standard deviation by the average. It helps compare variability relative to the size of the average.

El coeficiente de variación divide la desviación estándar entre el promedio. Ayuda a comparar la variabilidad en relación con el tamaño del promedio.

Python — coefficient of variation
# Coeficiente de variación de las ventas

average_sales = df["total"].mean()
std_sales = df["total"].std()

coefficient_variation = std_sales / average_sales

print("Sales coefficient of variation")
print("--------------------------------")
print(f"Average sales: ${average_sales:,.2f}")
print(f"Standard deviation: ${std_sales:,.2f}")
print(f"Coefficient of variation: {coefficient_variation:.4f}")
print(f"Coefficient of variation (%): {coefficient_variation * 100:.2f}%")

18Calculate sales indicators by companyCalcular indicadores de ventas por compañía

Now we take the same three indicators and calculate them by company: average sales, standard deviation, and coefficient of variation. This is where the exercise starts feeling like real analysis.

Ahora tomamos los mismos tres indicadores y los calculamos por compañía: promedio de ventas, desviación estándar y coeficiente de variación. Aquí el ejercicio empieza a sentirse como análisis real.

Python — average, standard deviation, and coefficient of variation by company
# Indicadores de ventas por compañía

company_sales_summary = (
    df.groupby("company_name")
    .agg(
        total_sales=("total", "sum"),
        average_sales=("total", "mean"),
        standard_deviation=("total", "std"),
        records=("total", "count")
    )
    .reset_index()
)

# Coeficiente de variación = desviación estándar / promedio
company_sales_summary["coefficient_variation"] = (
    company_sales_summary["standard_deviation"] / company_sales_summary["average_sales"]
)

# Formato visual
company_sales_summary["total_sales"] = company_sales_summary["total_sales"].map("${:,.2f}".format)
company_sales_summary["average_sales"] = company_sales_summary["average_sales"].map("${:,.2f}".format)
company_sales_summary["standard_deviation"] = company_sales_summary["standard_deviation"].map(
    lambda x: "N/A" if pd.isna(x) else f"${x:,.2f}"
)
company_sales_summary["coefficient_variation"] = company_sales_summary["coefficient_variation"].map(
    lambda x: "N/A" if pd.isna(x) else f"{x:.2%}"
)

company_sales_summary

Note: If a company has only one record, the standard deviation may appear as N/A. That is normal because variability cannot be calculated from a single observation.

Nota: Si una compañía tiene un solo registro, la desviación estándar puede aparecer como N/A. Eso es normal porque no se puede calcular variabilidad con una sola observación.

What we learned

Qué aprendimos

Cleaning data is not one command. It is a sequence: inspect, standardize, convert, validate, export, validate the created files, and then analyze.

Limpiar datos no es un solo comando. Es una secuencia: inspeccionar, estandarizar, convertir, validar, exportar, validar los ficheros creados y luego analizar.

Best habit

Mejor hábito

Never delete or overwrite raw data. Keep the original file and create a clean copy.

Nunca borres ni sobrescribas la data original. Conserva el archivo original y crea una copia limpia.

Next step

Siguiente paso

Use the clean dataset for EDA: average sales, standard deviation, coefficient of variation, and company-level comparisons.

Usa el dataset limpio para EDA: promedio de ventas, desviación estándar, coeficiente de variación y comparaciones por compañía.