Use this skill when the user uploads Excel (.xlsx/.xls) or CSV files and wants to perform data analysis, generate statistics, create summaries, pivot tables, SQL queries, or any form of structured data exploration. Supports multi-sheet Excel workbooks, aggregation, filtering, joins, and exporting results to CSV/JSON/Markdown.
87%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
This skill analyzes user-uploaded Excel/CSV files using DuckDB — an in-process analytical SQL engine. It supports schema inspection, SQL-based querying, statistical summaries, and result export, all through a single Python script.
Prepare the Python environment before running analyses. Install the required packages explicitly using the same interpreter that will run the script:
python -m pip install duckdb openpyxlIf either package is missing, the script exits with a setup instruction; it does not invoke an installer or modify the environment. DuckDB's spatial extension is initialized only for .xlsx input. CSV and legacy .xls analysis do not install or load it, so they can run offline with an empty extension cache.
Legacy Excel 97–2003 (.xls) workbooks additionally require:
python -m pip install pandas xlrdThese optional packages are loaded only for .xls input. A missing package produces
an explicit setup instruction. Legacy workbooks are read sheet by sheet with pandas'
xlrd engine, preserving cell values (including literal NA strings and leading
zeros in text identifiers) and treating empty cells as nulls. Columns with
incompatible cell types, such as a date and a number, are stored as text with
nulls preserved. Compatible numeric types still produce numeric columns.
Completely empty sheets are skipped. The .xlsx reader
continues to use openpyxl and DuckDB's spatial extension. A legacy workbook that
cannot be opened is reported and skipped so remaining inputs can still be
analyzed. The command fails if no input produces a table.
When a user uploads data files and requests analysis, identify:
/mnt/user-data/uploads//mnt/user-dataFirst, inspect the uploaded file to understand its schema:
python /mnt/skills/public/data-analysis/scripts/analyze.py \
--files /mnt/user-data/uploads/data.xlsx \
--action inspectThis returns:
Based on the schema, construct SQL queries to answer the user's questions.
python /mnt/skills/public/data-analysis/scripts/analyze.py \
--files /mnt/user-data/uploads/data.xlsx \
--action query \
--sql "SELECT category, COUNT(*) as count, AVG(amount) as avg_amount FROM Sheet1 GROUP BY category ORDER BY count DESC"python /mnt/skills/public/data-analysis/scripts/analyze.py \
--files /mnt/user-data/uploads/data.xlsx \
--action summary \
--table Sheet1This returns for each numeric column: count, mean, std, min, 25%, 50%, 75%, max, null_count. For string columns: count, unique, top value, frequency, null_count.
python /mnt/skills/public/data-analysis/scripts/analyze.py \
--files /mnt/user-data/uploads/data.xlsx \
--action query \
--sql "SELECT * FROM Sheet1 WHERE amount > 1000" \
--output-file /mnt/user-data/outputs/filtered-results.csvSupported output formats (auto-detected from extension):
.csv — Comma-separated values.json — JSON array of records.md — Markdown tableRelative output paths are resolved from the current working directory. A bare
filename such as report.csv writes there directly; missing parent directories
in paths such as reports/report.csv are created automatically.
An existing file at the target path is overwritten without warning.
JSON result column names must be unique. Use explicit SQL aliases for repeated
names, for example SELECT a.id AS left_id, b.id AS right_id FROM ....
Duplicate names cause an error and leave any existing output file untouched.
The error lists the repeated names so you can alias them directly. The CLI prints
a short Error: ... message to stderr and exits nonzero for this rejection.
JSON exports also reject non-finite numbers (NaN, Infinity, and -Infinity),
including values inside lists or structs, before opening the output file. Filter
or replace them in SQL, for example CASE WHEN isfinite(ratio) THEN ratio ELSE NULL END AS ratio. Finite numbers, nulls, and text such as 'NaN' remain valid.
| Parameter | Required | Description |
|---|---|---|
--files | Yes | Space-separated paths to Excel/CSV files |
--action | Yes | One of: inspect, query, summary |
--sql | For query | SQL query to execute |
--table | For summary | Table/sheet name to summarize |
--output-file | No | Path to export results (CSV/JSON/MD) |
[!NOTE] Do NOT read the Python file, just call it with the parameters.
Sheet1, Sales, Revenue)data.csv → data)report, report_1, report_2. This also applies to repeated Excel sheet names. Use the SQL names shown by inspect for queries and summaries. An original-name alias never overrides an earlier SQL name.sales-2026 → sales_2026). Use the SQL names printed by inspect, or double-quote a unique original name, e.g., SELECT * FROM "sales-2026". Queries execute unchanged: string literals, column aliases, comments, and CTEs retain normal DuckDB semantics. Names containing a double quote escape it by doubling it, e.g., "net""amount"-- Row count
SELECT COUNT(*) FROM Sheet1
-- Distinct values in a column
SELECT DISTINCT category FROM Sheet1
-- Value distribution
SELECT category, COUNT(*) as cnt FROM Sheet1 GROUP BY category ORDER BY cnt DESC
-- Date range
SELECT MIN(date_col), MAX(date_col) FROM Sheet1-- Revenue by category and month
SELECT category, DATE_TRUNC('month', order_date) as month,
SUM(revenue) as total_revenue
FROM Sales
GROUP BY category, month
ORDER BY month, total_revenue DESC
-- Top 10 customers by spend
SELECT customer_name, SUM(amount) as total_spend
FROM Orders GROUP BY customer_name
ORDER BY total_spend DESC LIMIT 10-- Join sales with customer info from different files
SELECT s.order_id, s.amount, c.customer_name, c.region
FROM sales s
JOIN customers c ON s.customer_id = c.id
WHERE s.amount > 500-- Running total and rank
SELECT order_date, amount,
SUM(amount) OVER (ORDER BY order_date) as running_total,
RANK() OVER (ORDER BY amount DESC) as amount_rank
FROM Sales-- Pivot: monthly revenue by category
SELECT category,
SUM(CASE WHEN MONTH(date) = 1 THEN revenue END) as Jan,
SUM(CASE WHEN MONTH(date) = 2 THEN revenue END) as Feb,
SUM(CASE WHEN MONTH(date) = 3 THEN revenue END) as Mar
FROM Sales
GROUP BY categoryUser uploads sales_2024.xlsx (with sheets: Orders, Products, Customers) and asks: "Analyze my sales data — show top products by revenue and monthly trends."
python /mnt/skills/public/data-analysis/scripts/analyze.py \
--files /mnt/user-data/uploads/sales_2024.xlsx \
--action inspectpython /mnt/skills/public/data-analysis/scripts/analyze.py \
--files /mnt/user-data/uploads/sales_2024.xlsx \
--action query \
--sql "SELECT p.product_name, SUM(o.quantity * o.unit_price) as total_revenue, SUM(o.quantity) as total_units FROM Orders o JOIN Products p ON o.product_id = p.id GROUP BY p.product_name ORDER BY total_revenue DESC LIMIT 10"python /mnt/skills/public/data-analysis/scripts/analyze.py \
--files /mnt/user-data/uploads/sales_2024.xlsx \
--action query \
--sql "SELECT DATE_TRUNC('month', order_date) as month, SUM(quantity * unit_price) as revenue FROM Orders GROUP BY month ORDER BY month" \
--output-file /mnt/user-data/outputs/monthly-trends.csvpython /mnt/skills/public/data-analysis/scripts/analyze.py \
--files /mnt/user-data/uploads/sales_2024.xlsx \
--action summary \
--table OrdersPresent results to the user with clear explanations of findings, trends, and actionable insights.
User uploads orders.csv and customers.xlsx and asks: "Which region has the highest average order value?"
python /mnt/skills/public/data-analysis/scripts/analyze.py \
--files /mnt/user-data/uploads/orders.csv /mnt/user-data/uploads/customers.xlsx \
--action query \
--sql "SELECT c.region, AVG(o.amount) as avg_order_value, COUNT(*) as order_count FROM orders o JOIN Customers c ON o.customer_id = c.id GROUP BY c.region ORDER BY avg_order_value DESC"After analysis:
present_files toolThe script automatically caches loaded data to avoid re-parsing files on every call:
Concurrent cold calls may build separate complete generations. Failed private
builds are removed, while complete generations are retained so active readers
remain valid. Stop analyses before manually clearing .data-analysis-cache/;
automatic cache garbage collection is not implemented. These are rebuildable
local caches, not snapshots of changing input files or a power-loss guarantee.
Analysis connections are read-only; export query results with --output-file.
This is especially useful when running multiple queries against the same data files (inspect → query → summary).
DATE_TRUNC, EXTRACT, etc.)"Column Name"bytedance/deer-flow
085b52b
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.