Data analysis
Skill furkangonel/cowrangler/bundled_skills/data-science/data-analysis
Autonomous terminal AI agent for workflows and feasible project procedures. Co-Worker Co-Wrangler π
npx -y skills add furkangonel/cowrangler --skill data-analysisAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Structured data analysis workflow from raw data to shareable insights.
SKILL.md
10.6 KB, as published. Nobody here has run it
Data Analysis SOP
When to Use
- User wants to analyze a dataset and find patterns or insights
- User asks for EDA (Exploratory Data Analysis) on a file
- User wants summary statistics, distributions, or correlations
- User needs to clean dirty data before analysis
Part 1 β The Analysis Workflow
1. Load & Inspect β understand what you have
2. Clean β handle nulls, types, duplicates, outliers
3. Explore (EDA) β distributions, correlations, group comparisons
4. Hypothesize β state specific questions to answer
5. Validate β test hypotheses with statistics or aggregations
6. Communicate β clear charts + written findings
Part 2 β Load & Inspect
import pandas as pd
import numpy as np
# ββ Load ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
df = pd.read_csv("data.csv", parse_dates=["date_col"])
# For Excel: pd.read_excel("data.xlsx", sheet_name="Sheet1")
# For JSON: pd.read_json("data.json", lines=True) # JSONL
# For large files: pd.read_csv("data.csv", chunksize=100_000)
# ββ Quick overview ββββββββββββββββββββββββββββββββββββββββββββββββ
print(f"Shape: {df.shape[0]:,} rows Γ {df.shape[1]} columns")
print(f"Memory: {df.memory_usage(deep=True).sum() / 1e6:.1f} MB")
print()
print(df.dtypes)
print()
df.head(3)
Inspection Checklist
# 1. Types β are columns the right dtype?
df.dtypes
# 2. Nulls
null_report = pd.DataFrame({
"null_count": df.isnull().sum(),
"null_pct": (df.isnull().mean() * 100).round(1)
}).query("null_count > 0").sort_values("null_pct", ascending=False)
print(null_report)
# 3. Duplicates
dup_count = df.duplicated().sum()
print(f"Full duplicates: {dup_count} ({dup_count/len(df)*100:.1f}%)")
# 4. Cardinality β how many unique values per column?
df.nunique().sort_values(ascending=False)
# 5. Value ranges for numerics
df.describe().T.round(2)
Part 3 β Cleaning
# ββ Fix dtypes ββββββββββββββββββββββββββββββββββββββββββββββββββββ
df["date"] = pd.to_datetime(df["date"], errors="coerce")
df["amount"] = pd.to_numeric(df["amount"], errors="coerce")
df["category"] = df["category"].astype("category")
# ββ Standardize strings βββββββββββββββββββββββββββββββββββββββββββ
df["name"] = df["name"].str.strip().str.lower()
# ββ Remove exact duplicates βββββββββββββββββββββββββββββββββββββββ
df = df.drop_duplicates()
# ββ Handle nulls (choose strategy per column) βββββββββββββββββββββ
# Drop rows where critical column is null
df = df.dropna(subset=["user_id", "event_type"])
# Fill with median (numeric)
df["revenue"] = df["revenue"].fillna(df["revenue"].median())
# Fill with mode (categorical)
df["country"] = df["country"].fillna(df["country"].mode()[0])
# Fill forward (time series)
df = df.sort_values("date")
df["price"] = df["price"].ffill()
# ββ Handle outliers βββββββββββββββββββββββββββββββββββββββββββββββ
# IQR method β cap rather than drop
Q1 = df["amount"].quantile(0.25)
Q3 = df["amount"].quantile(0.75)
IQR = Q3 - Q1
lower, upper = Q1 - 1.5 * IQR, Q3 + 1.5 * IQR
df["amount_capped"] = df["amount"].clip(lower, upper)
# Z-score method β flag extreme outliers
from scipy import stats
df["amount_zscore"] = np.abs(stats.zscore(df["amount"].dropna()))
outliers = df[df["amount_zscore"] > 3]
print(f"Outliers (|z|>3): {len(outliers)}")
Part 4 β EDA Patterns
Distribution β Single Numeric Column
import matplotlib.pyplot as plt
import seaborn as sns
col = "revenue"
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
# Histogram
axes[0].hist(df[col].dropna(), bins=40, edgecolor="white", color="#4c72b0")
axes[0].set_title(f"{col} β Distribution")
axes[0].set_xlabel(col)
# Box plot
axes[1].boxplot(df[col].dropna(), patch_artist=True,
boxprops=dict(facecolor="#4c72b0", alpha=0.6))
axes[1].set_title(f"{col} β Box Plot")
# Cumulative distribution
sorted_vals = df[col].dropna().sort_values()
axes[2].plot(sorted_vals.values, np.linspace(0, 1, len(sorted_vals)))
axes[2].set_title(f"{col} β CDF")
axes[2].set_xlabel(col)
axes[2].set_ylabel("Cumulative Probability")
plt.tight_layout()
plt.show()
# Key stats
print(df[col].describe())
print(f"Skewness: {df[col].skew():.3f}")
print(f"Kurtosis: {df[col].kurtosis():.3f}")
Group Comparison
# Compare a metric across categories
group_col = "segment"
metric_col = "revenue"
grouped = df.groupby(group_col)[metric_col].agg(
count="count",
mean="mean",
median="median",
std="std",
total="sum"
).round(2).sort_values("mean", ascending=False)
print(grouped)
# Visualize
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
grouped["mean"].plot(kind="bar", ax=axes[0], color="#4c72b0")
axes[0].set_title(f"Mean {metric_col} by {group_col}")
axes[0].tick_params(axis="x", rotation=45)
sns.boxplot(data=df, x=group_col, y=metric_col, ax=axes[1])
axes[1].set_title(f"{metric_col} Distribution by {group_col}")
axes[1].tick_params(axis="x", rotation=45)
plt.tight_layout()
plt.show()
Correlation Analysis
numeric_cols = df.select_dtypes(include=np.number).columns.tolist()
corr = df[numeric_cols].corr()
# Heatmap
fig, ax = plt.subplots(figsize=(10, 8))
mask = np.triu(np.ones_like(corr, dtype=bool))
sns.heatmap(corr, mask=mask, annot=True, fmt=".2f",
cmap="RdBu_r", center=0, vmin=-1, vmax=1,
square=True, linewidths=0.5, ax=ax)
plt.title("Correlation Matrix")
plt.tight_layout()
plt.show()
# Top correlations with a target
target = "revenue"
top_corr = corr[target].drop(target).abs().sort_values(ascending=False)
print(f"\nTop correlations with {target}:\n{top_corr.head(10)}")
Time Series Pattern
date_col = "date"
metric_col = "revenue"
df = df.sort_values(date_col)
# Aggregate by time period
daily = df.groupby(pd.Grouper(key=date_col, freq="D"))[metric_col].sum()
weekly = df.groupby(pd.Grouper(key=date_col, freq="W"))[metric_col].sum()
monthly = df.groupby(pd.Grouper(key=date_col, freq="ME"))[metric_col].sum()
fig, axes = plt.subplots(3, 1, figsize=(14, 12))
daily.plot(ax=axes[0], title="Daily")
weekly.plot(ax=axes[1], title="Weekly")
monthly.plot(kind="bar", ax=axes[2], title="Monthly")
plt.tight_layout()
plt.show()
# 7-day rolling average
df["revenue_7d_avg"] = daily.rolling(7).mean()
Part 5 β Choosing the Right Visualization
| Data type | Comparison | Best chart |
|---|---|---|
| Numeric distribution | Single | Histogram, KDE |
| Numeric distribution | Two groups | Overlapping hist, KDE, violin |
| Numeric distribution | Many groups | Box plot, violin plot |
| Category vs Numeric | Few categories | Bar chart (mean + error bars) |
| Category vs Numeric | Many categories | Horizontal bar chart |
| Two numeric cols | Correlation | Scatter plot |
| Three numeric cols | Correlation | Scatter + color/size encoding |
| Time + Numeric | Trend | Line chart |
| Time + Category | Composition | Stacked area, stacked bar |
| Part-of-whole | < 5 parts | Pie chart (sparingly) |
| Part-of-whole | Many parts | Stacked bar, treemap |
Part 6 β Sharing Findings
Structure every finding as:
## Finding: [Specific, concrete title]
**What:** [One sentence describing the pattern observed]
**Data:** [Which columns, time range, segment, sample size]
**Evidence:**
[Chart or table]
**Magnitude:** [How big is the effect? Revenue impact? % difference?]
**Implication:** [What decision or action does this enable?]
**Confidence:** [How certain are you? Any caveats?]
Example Finding
## Finding: Enterprise customers have 3.2Γ higher ARPU than SMB
**What:** Enterprise segment generates $4,800 average monthly revenue vs $1,500 for SMB.
**Data:** revenue table, JanβApr 2025, n=2,340 customers (412 Enterprise, 1,928 SMB)
**Evidence:** [bar chart showing revenue by segment]
**Magnitude:** $3,300 delta per customer per month; Enterprise is 18% of customers but 37% of revenue.
**Implication:** Prioritize Enterprise acquisition and expansion β higher ROI per sales hour.
**Confidence:** High β consistent across all 4 months. Outliers (>$50k) removed; median tells same story.
Part 7 β Quick Summary Statistics Function
def quick_summary(df: pd.DataFrame, target: str = None) -> None:
"""Print a structured EDA summary for a DataFrame."""
print(f"{'='*60}")
print(f"DATASET SUMMARY")
print(f"{'='*60}")
print(f"Shape: {df.shape[0]:,} rows Γ {df.shape[1]} columns")
print(f"Memory: {df.memory_usage(deep=True).sum() / 1e6:.1f} MB")
print(f"Nulls: {df.isnull().sum().sum():,} total null values")
print(f"Dupes: {df.duplicated().sum():,} duplicate rows")
print()
num_cols = df.select_dtypes(include=np.number).columns.tolist()
cat_cols = df.select_dtypes(include=["object", "category"]).columns.tolist()
date_cols = df.select_dtypes(include="datetime").columns.tolist()
print(f"Numeric ({len(num_cols)}): {', '.join(num_cols)}")
print(f"Categorical ({len(cat_cols)}): {', '.join(cat_cols)}")
print(f"Datetime ({len(date_cols)}): {', '.join(date_cols)}")
if target and target in num_cols:
print(f"\nTarget: {target}")
print(df[target].describe().round(2))
Agent Instructions
- Always start with shape, nulls, dtypes β never skip the inspection phase
- Ask what the user wants to know before diving into analysis β the question drives the method
- When summarizing findings, always include sample size and caveats
- For any group comparison, check if groups have very different sizes β it affects interpretation
- When correlations are found, remind the user: correlation β causation
- Use concrete numbers in findings: "$X more", "2.3Γ higher", "fell by 15%" β not just "higher" or "lower"
- If data has a date column, always check for time trends before drawing conclusions from overall averages