- Posted on
- • Artificial Intelligence
Artificial Intelligence Data Processing with Python
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Artificial Intelligence Data Processing with Python: A Linux Bash-first Playbook
Your model is only as good as your data pipeline. Most wins (and most failures) in AI happen before the first epoch: ingestion, cleaning, validation, feature engineering, and efficient storage. This post gives you a practical, Bash-first workflow for building reliable, fast Python data pipelines on Linux—so you can spend less time wrangling and more time modeling.
What you’ll get:
Why Python + Bash is a power combo for AI data prep
A minimal, reproducible toolkit you can install with apt, dnf, or zypper
3–5 actionable steps with real-world examples and copy-paste code
A clear next step to productionize your pipeline
Why this matters (and why Python + Bash)
Reproducibility: Bash scripts + pinned Python environments make your pipeline deterministic and automatable (cron, systemd, CI).
Speed and cost: Efficient formats (Parquet), columnar processing, and streaming/chunking reduce memory and CPU.
Ecosystem leverage: Bash for orchestration and OS-level tooling; Python for rich data libraries (pandas, scikit-learn, pyarrow, polars).
0) Install the tooling
Install the base system packages. Choose your distro:
- Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y \
python3 python3-venv python3-pip python3-dev build-essential \
git curl jq parallel zstd pigz pkg-config libopenblas-dev
- Fedora/RHEL/CentOS Stream (dnf):
sudo dnf install -y \
python3 python3-pip python3-devel gcc gcc-c++ make \
git curl jq parallel zstd pigz openblas-devel pkgconf-pkg-config
- openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y \
python3 python3-pip python3-devel gcc gcc-c++ make \
git curl jq zstd pigz openblas-devel pkgconf-pkg-config
# Note: GNU Parallel package may be named 'gnu_parallel' on some openSUSE versions:
# sudo zypper install -y parallel || sudo zypper install -y gnu_parallel
Create and populate a Python virtual environment:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip wheel
pip install \
numpy pandas pyarrow fastparquet polars \
scikit-learn scipy pillow pandera datasets \
csvkit
Verify:
python3 --version
python - <<'PY'
import numpy, pandas, pyarrow, sklearn, polars, pandera
print("Python data stack OK")
PY
Project skeleton:
mkdir -p data/{raw,staged,processed,cache} scripts logs
1) Ingest and validate your data (fast, safe, repeatable)
Pull data and sanity-check it before you touch a line of Python.
- Download + decompress + quick peek:
URL="https://example.org/logs.csv.zst"
curl -L "$URL" -o data/raw/logs.csv.zst
zstd -d --force data/raw/logs.csv.zst -o data/raw/logs.csv
head -n 5 data/raw/logs.csv
sha256sum data/raw/logs.csv | tee data/raw/logs.csv.sha256
- Quick stats with CLI tools (great for spot-checks in CI):
csvstat data/raw/logs.csv | head -n 40
jq --version # keep handy for JSON lines streams
- Schema validation with Python to catch problems early:
# scripts/validate.py
import pandas as pd
import pandera as pa
from pandera import Column, Check
schema = pa.DataFrameSchema({
"ts": Column(pa.DateTime, coerce=True),
"user_id": Column(pa.String, nullable=False),
"latency_ms": Column(pa.Float, Check.ge(0)),
"status": Column(pa.Int, Check.isin([200, 301, 302, 400, 401, 403, 404, 500])),
"path": Column(pa.String)
})
df = pd.read_csv("data/raw/logs.csv")
schema.validate(df, lazy=True)
df.to_parquet("data/staged/logs.parquet", engine="pyarrow", index=False)
print("Validated and staged → data/staged/logs.parquet")
Run it:
source .venv/bin/activate
python scripts/validate.py
Why Parquet now? It’s columnar, compressed, and fast for downstream transforms and training.
2) Clean and transform to model-ready form
Normalize types, handle outliers/missing values, and add lightweight derived columns.
# scripts/clean_transform.py
import pandas as pd
import numpy as np
def load_and_fix(path: str) -> pd.DataFrame:
df = pd.read_parquet(path)
# Derived features
df["hour"] = df["ts"].dt.floor("H")
df["is_error"] = (df["status"] >= 400).astype(np.int8)
# Clip and impute
df["latency_ms"] = df["latency_ms"].clip(0, 10000)
df = df.dropna(subset=["user_id", "path"])
return df
df = load_and_fix("data/staged/logs.parquet")
df.to_parquet("data/processed/logs_clean.parquet", engine="pyarrow", index=False)
print("Cleaned → data/processed/logs_clean.parquet")
Large files? Stream in chunks:
# scripts/csv_to_parquet_chunked.py
import pandas as pd
out = "data/staged/logs.parquet"
writer = None
for chunk in pd.read_csv("data/raw/logs.csv", chunksize=500_000, parse_dates=["ts"]):
if writer is None:
chunk.to_parquet(out, engine="pyarrow", index=False)
else:
chunk.to_parquet(out, engine="pyarrow", index=False, append=True) # requires pyarrow >= 14
writer = True
print("Chunked conversion complete.")
Or go even faster with Polars:
# scripts/csv_to_parquet_polars.py
import polars as pl
(
pl.scan_csv("data/raw/logs.csv", infer_schema_length=10000, try_parse_dates=True)
.with_columns([
pl.col("status").cast(pl.Int32),
pl.col("latency_ms").cast(pl.Float64).clip_min(0)
])
.collect()
.write_parquet("data/staged/logs.parquet")
)
3) Feature engineering that scales
Tabular example (numerical + categorical) into sparse matrices:
# scripts/features_tabular.py
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.pipeline import Pipeline
from scipy import sparse
import numpy as np
df = pd.read_parquet("data/processed/logs_clean.parquet")
y = df["is_error"].to_numpy()
X_df = df[["latency_ms", "hour", "path", "user_id"]].copy()
X_df["hour"] = (pd.to_datetime(X_df["hour"]).astype("int64") // 10**9)
ct = ColumnTransformer(
transformers=[
("num", StandardScaler(with_mean=True), ["latency_ms", "hour"]),
("cat", OneHotEncoder(handle_unknown="ignore", min_frequency=10), ["path", "user_id"]),
],
)
X = ct.fit_transform(X_df)
sparse.save_npz("data/processed/X.npz", X)
np.save("data/processed/y.npy", y)
print("Saved features to data/processed/X.npz and labels to data/processed/y.npy")
Text to TF-IDF (solid baseline, fast to train):
# scripts/text_to_tfidf.py
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from scipy import sparse
df = pd.read_csv("data/raw/reviews.csv")
tfidf = TfidfVectorizer(lowercase=True, max_features=50000, ngram_range=(1,2))
X = tfidf.fit_transform(df["review_text"].fillna(""))
sparse.save_npz("data/processed/reviews_tfidf.npz", X)
df["label"].to_csv("data/processed/reviews_labels.csv", index=False)
print("Text features ready.")
Images: standardize size and color space for CNNs:
# scripts/resize_images.py
from PIL import Image
from pathlib import Path
src = Path("data/raw/images")
dst = Path("data/processed/images_224")
dst.mkdir(parents=True, exist_ok=True)
for p in src.rglob("*.jpg"):
im = Image.open(p).convert("RGB").resize((224, 224))
out = dst / p.relative_to(src)
out.parent.mkdir(parents=True, exist_ok=True)
im.save(out, quality=95)
print("Resized images → data/processed/images_224")
4) Parallelize and cache for speed
Sharding + GNU Parallel multiplies throughput on multicore machines.
- Convert multiple CSV shards to Parquet in parallel:
# scripts/convert_one.py
import sys, pandas as pd
src, dst = sys.argv[1], sys.argv[2]
pd.read_csv(src).to_parquet(dst, engine="pyarrow", index=False)
print(f"{src} → {dst}")
Run it:
ls data/raw/*.csv | parallel --bar 'python scripts/convert_one.py {} data/staged/{/.}.parquet'
- Keep a manifest for reproducibility:
date -u +"%Y-%m-%dT%H:%M:%SZ" | tee data/processed/run_timestamp.txt
sha256sum data/processed/* | tee data/processed/manifest.sha256
- Cache expensive steps (simple convention):
[ -f data/staged/logs.parquet ] || python scripts/csv_to_parquet_chunked.py
[ -f data/processed/logs_clean.parquet ] || python scripts/clean_transform.py
5) Real-world mini pipelines
Web log anomaly dataset:
- Ingest logs → validate schema → clip latencies → error flag → one-hot path/user → save X.npz/y.npy.
- Train a quick baseline (e.g., LogisticRegression) before investing in deep models.
Image classification:
- rsync/curl the archive → verify sha256 → resize to 224×224 → split into train/val/test → write CSV manifest of file paths and labels.
Text sentiment:
- Normalize quotes/whitespace → drop empties → TF-IDF with n-grams → sparse save → stratified split.
Optional: Makefile to glue it together
# Makefile (run with: make setup ingest validate transform features)
VENV=.venv
ACT=source $(VENV)/bin/activate
setup:
python3 -m venv $(VENV)
$(ACT) && pip install -U pip wheel
$(ACT) && pip install numpy pandas pyarrow fastparquet polars scikit-learn scipy pillow pandera datasets csvkit
ingest:
mkdir -p data/{raw,staged,processed,cache}
curl -L $(URL) -o data/raw/data.csv.zst
zstd -d --force data/raw/data.csv.zst -o data/raw/data.csv
sha256sum data/raw/data.csv > data/raw/data.csv.sha256
validate:
$(ACT) && python scripts/validate.py
transform:
$(ACT) && python scripts/clean_transform.py
features:
$(ACT) && python scripts/features_tabular.py
Conclusion and next steps (CTA)
Data pipelines win championships. With a Bash-first mindset and Python’s data stack, you can:
Ingest and validate deterministically
Transform at scale with columnar formats
Engineer features that are fast to train on
Your next step:
Pick one of the examples above and wire it end-to-end in your project. Start with Parquet + a simple feature script. Add parallelism once it works.
Productionize: add a Makefile target per step, schedule with cron/systemd, and log outputs to the logs/ directory.
Version your data artifacts (e.g., via checksums and dated folders) and pin your Python dependencies (requirements.txt).
If you want a quick win, copy the validate → transform → features scripts into your repo, adapt column names to your data, and run them today. Your future training runs will thank you.