257 lines
6.0 KiB
Plaintext
257 lines
6.0 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "97d2f264",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Module 2 — Lab 5: Pandas — Loading, Inspecting, and Cleaning Logs\n",
|
|
"\n",
|
|
"\n",
|
|
"**Topics:** CSV, DataFrame, missing values, duplicates, datetime, filtering\n",
|
|
"\n",
|
|
"\n",
|
|
"> **Solution version:** This notebook contains the complete solution. Run the cells in order and compare the results with your Lab notebook.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "7b1ce82e",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Scenario\n",
|
|
"\n",
|
|
"The `data/auth_log.csv` file contains duplicate rows, invalid timestamps, missing critical fields, and inconsistent capitalization. The goal is to produce a clean DataFrame ready for analysis.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "9dea9739",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from pathlib import Path\n",
|
|
"import pandas as pd\n",
|
|
"\n",
|
|
"DATA_PATH = Path(\"data/auth_log.csv\")\n",
|
|
"OUTPUT_DIR = Path(\"output\")\n",
|
|
"OUTPUT_DIR.mkdir(exist_ok=True)\n",
|
|
"\n",
|
|
"log_df = pd.read_csv(DATA_PATH)\n",
|
|
"log_df.head()\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "6e6da8ed",
|
|
"metadata": {
|
|
"tags": [
|
|
"exercise"
|
|
]
|
|
},
|
|
"source": [
|
|
"## 1. Inspect data quality\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "c9ef821c",
|
|
"metadata": {
|
|
"tags": [
|
|
"solution"
|
|
]
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"print(\"Shape:\", log_df.shape)\n",
|
|
"print(\"Duplicate rows:\", log_df.duplicated().sum())\n",
|
|
"print(\"Missing values by column:\")\n",
|
|
"print(log_df.isna().sum())\n",
|
|
"print(\"\\nData types:\")\n",
|
|
"print(log_df.dtypes)\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "6b759b1c",
|
|
"metadata": {
|
|
"tags": [
|
|
"exercise"
|
|
]
|
|
},
|
|
"source": [
|
|
"## 2. Remove duplicates and normalize timestamps and critical fields\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "6d4342ee",
|
|
"metadata": {
|
|
"tags": [
|
|
"solution"
|
|
]
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"clean_df = log_df.copy()\n",
|
|
"clean_df = clean_df.drop_duplicates()\n",
|
|
"\n",
|
|
"clean_df[\"timestamp\"] = pd.to_datetime(\n",
|
|
" clean_df[\"timestamp\"], errors=\"coerce\", utc=True\n",
|
|
")\n",
|
|
"\n",
|
|
"clean_df = clean_df.dropna(\n",
|
|
" subset=[\"timestamp\", \"source_ip\", \"status\"]\n",
|
|
")\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "c5c59040",
|
|
"metadata": {
|
|
"tags": [
|
|
"exercise"
|
|
]
|
|
},
|
|
"source": [
|
|
"## 3. Normalize text and create time-based fields\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "012ba902",
|
|
"metadata": {
|
|
"tags": [
|
|
"solution"
|
|
]
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"clean_df[\"source_ip\"] = clean_df[\"source_ip\"].str.strip()\n",
|
|
"clean_df[\"username\"] = clean_df[\"username\"].fillna(\"unknown\").str.strip().str.lower()\n",
|
|
"clean_df[\"status\"] = clean_df[\"status\"].str.strip().str.upper()\n",
|
|
"\n",
|
|
"clean_df = clean_df[clean_df[\"status\"].isin([\"SUCCESS\", \"FAILED\"])]\n",
|
|
"clean_df[\"hour\"] = clean_df[\"timestamp\"].dt.hour\n",
|
|
"clean_df = clean_df.sort_values(\"timestamp\").reset_index(drop=True)\n",
|
|
"\n",
|
|
"clean_df.head()\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "354fc8c0",
|
|
"metadata": {
|
|
"tags": [
|
|
"exercise"
|
|
]
|
|
},
|
|
"source": [
|
|
"## 4. Filter and aggregate\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "38f69103",
|
|
"metadata": {
|
|
"tags": [
|
|
"solution"
|
|
]
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"failed_df = clean_df[clean_df[\"status\"] == \"FAILED\"].copy()\n",
|
|
"failed_by_ip = failed_df[\"source_ip\"].value_counts().rename(\"failed_attempts\")\n",
|
|
"status_counts = clean_df[\"status\"].value_counts()\n",
|
|
"\n",
|
|
"print(\"Status counts:\")\n",
|
|
"print(status_counts)\n",
|
|
"print(\"\\nFailed attempts by IP:\")\n",
|
|
"print(failed_by_ip)\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "748f22a9",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 5. Export clean data\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "fdcb2bf7",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"clean_df.to_csv(OUTPUT_DIR / \"clean_auth_log.csv\", index=False)\n",
|
|
"clean_df.to_json(\n",
|
|
" OUTPUT_DIR / \"clean_auth_log.json\",\n",
|
|
" orient=\"records\",\n",
|
|
" indent=2,\n",
|
|
" date_format=\"iso\",\n",
|
|
")\n",
|
|
"failed_by_ip.to_csv(\n",
|
|
" OUTPUT_DIR / \"failed_login_by_ip.csv\",\n",
|
|
" header=True,\n",
|
|
")\n",
|
|
"\n",
|
|
"print(\"Exported files to:\", OUTPUT_DIR.resolve())\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "4b07b67e",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Self-check\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "b16f153c",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"assert log_df.duplicated().sum() >= 1\n",
|
|
"assert clean_df.duplicated().sum() == 0\n",
|
|
"assert clean_df[\"timestamp\"].isna().sum() == 0\n",
|
|
"assert set(clean_df[\"status\"]) <= {\"SUCCESS\", \"FAILED\"}\n",
|
|
"assert clean_df[\"username\"].str.strip().eq(clean_df[\"username\"]).all()\n",
|
|
"assert failed_by_ip.index[0] == \"198.51.100.14\"\n",
|
|
"assert (OUTPUT_DIR / \"clean_auth_log.csv\").exists()\n",
|
|
"assert (OUTPUT_DIR / \"clean_auth_log.json\").exists()\n",
|
|
"print(\"✓ Lab 5 completed\")\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3 (ipykernel)",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.11.6"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|