Data profiling
Data profiling: profile a CSV or Parquet file in your browser
rows.page profiles CSV, TSV, JSON and Parquet files of up to 1.5 GB in your browser, and DuckDB's SUMMARIZE adds 12 statistics per column in one query. It's free with no signup, and nothing is uploaded unless you click Share.
No file handy? Open CO2 emissions by country and year (50k rows, 14 MB).
What a data profile checks
Before you load a new file into a database or a script, a profile shows you the shape of every column, so you find the problems first:
- Types: whether a column parsed as a number, date or boolean, or ended up as text because of a few bad values.
- Nulls: how much of each column is missing.
- Distinct counts: whether an ID column is really unique, or a status column has unexpected categories.
- Ranges: the lowest and highest numbers and dates.
- Distributions: a histogram for numbers or the most common values for text.
- Duplicates: whole rows or keys that appear more than once.
- Outliers: values far outside the normal range.
Profile a file with the rows.page Columns panel
- Open
https://rows.pageand drop your CSV, TSV, JSON, JSONL or Parquet file on the page, or click Paste data to paste cells copied from a spreadsheet. - Open the Columns panel. Each column gets a card with its profile.
- Click a histogram bar or a top value to filter the grid down to those rows.
Each card shows the detected type, the distinct count ("N values", "~N values" when estimated on large files, or "unique") and the share of nulls ("N% null"). Numbers and dates get their min and max plus a histogram; text columns get their top 10 values and a "Search all N values" box for the rest.
Clicking a bar writes a range like price:10..20 into the search box, and clicking a value writes a filter like country:NL. The Has value and Is empty buttons write column:* and -column:*. The counts on each card always describe the whole file, while the charts follow the other filters you set.
The panel doesn't show the mean, median or standard deviation, and it doesn't count duplicate rows. Use SQL for those:
| Check | Where in rows.page | SQL alternative |
|---|---|---|
| Data type | Columns panel | SUMMARIZE (column_type) |
| Null rate | Columns panel ("N% null") or Is empty (-col:*) | SUMMARIZE (null_percentage) |
| Distinct count | Columns panel ("N values" or "unique") | SELECT count(DISTINCT col) FROM data |
| Min and max | Columns panel (numbers and dates) | SUMMARIZE (min, max) |
| Top values | Columns panel (histogram or top 10) | SELECT col, count(*) FROM data GROUP BY 1 ORDER BY 2 DESC |
| Mean, median, std dev | Not in the panel | SELECT avg(col), median(col), stddev(col) FROM data |
| Quartiles | Not in the panel | SUMMARIZE (q25, q50, q75) |
| Duplicate rows | Not in the panel | SELECT * EXCLUDE (__rid), count(*) FROM data GROUP BY ALL HAVING count(*) > 1 |
Get averages, medians and quartiles with SQL
Type SQL that starts with SELECT, WITH or FROM in the search box. The file is the table data, and results show up to 1,000,000 rows. For a full profile of every column, run:
FROM (SUMMARIZE SELECT * EXCLUDE (__rid) FROM data)You get one row per column with 12 fields: column_name, column_type, min, max, approx_unique, avg, std, q25, q50, q75, count and null_percentage. EXCLUDE (__rid) keeps out the hidden row number rows.page adds, and SUMMARIZE data on its own is read as a filter, so keep the FROM (...) around it.
The quartiles are estimates. For an exact median, or stats on one column, query it directly, with dot-path names from nested JSON in double quotes, like "user.age":
SELECT
avg(price),
median(price),
stddev(price)
FROM dataTo find keys that should be unique but aren't:
SELECT id, count(*) AS occurrences
FROM data
GROUP BY id
HAVING count(*) > 1
ORDER BY occurrences DESCrows.page vs ydata-profiling
ydata-profiling, formerly pandas-profiling, is a Python library that builds an HTML report from a pandas DataFrame. It fits when you already work in a notebook or a Python pipeline.
- Setup: ydata-profiling needs Python, the package and a DataFrame; rows.page runs in a browser tab with no install or account.
- Follow-up: the report is a static page, while in rows.page a click on a chart filters the rows behind it, and you can query them with SQL in the same box.
- Formats: rows.page opens CSV, TSV, JSON, JSONL and Parquet, and turns nested JSON into dot-path columns, up to 8 levels deep. It doesn't read Excel files, so export those to CSV first.
- Scope: rows.page draws per-column charts only, and it can't join two files or compare two local files side by side.
Clean, export and share what you found
You can't edit cells in rows.page. To clean the data, filter the view, for example price:>0 country:*, or write a SELECT that fixes and deduplicates the rows. Then open the Export menu and save CSV, Parquet (zstd), JSON, JSONL or Markdown (first 1,000 rows). A filtered view exports every matching row; a SQL result exports up to 1,000,000.
To show a teammate what you found, click Share, or push the file from a terminal:
curl -T data.csv https://rows.pageYou get a https://rows.page/<id> link. Your filter or SQL, sort and visible columns are kept in the link, so Copy link to this view shares the exact slice of bad rows. A single push takes up to 100 MB, and without an account the link expires after 7 days.
rows.page limits and prices
| What | Limit or price |
|---|---|
| Open a file in your browser | Files up to 1.5 GB, $0, no account, nothing uploaded |
| Share a link without an account | $0, files up to 100 MB, link deleted 7 days after creation |
| Free account | $0, 10 datasets kept, last 3 versions |
| Pro | $12/month, 100 datasets, files up to 500 MB, private links |
| Max | $39/month, 1,000 datasets, files up to 2 GB, private links |
Questions
How big a file can I profile in rows.page?
Up to 1.5 GB per browser tab. A .json file over 1.5 GB is refused with "Too big for this browser", while CSV, TSV and JSONL files over 1.5 GB load their first rows. Parquet isn't held to the 1.5 GB check.
Does rows.page upload my file when I profile it?
No. DuckDB-WASM reads the file inside your browser tab. Nothing is uploaded unless you click Share or push the file with curl -T or the MCP server.
Can I profile a file on S3, GitHub or Google Sheets without downloading it?
Yes, put the link after https://rows.page/, such as a raw.githubusercontent.com link, an S3 presigned URL or a Google Sheets export?format=csv link. If the host blocks cross-origin requests, rows.page fetches it through its proxy, up to 100 MB and 30 seconds, without keeping a copy.
How does rows.page handle delimiters and text encodings?
It detects the delimiter and header row on its own. CSV files are read as UTF-8, or UTF-16 with a BOM; a file that isn't valid UTF-8 is read as Latin-1, but Windows characters such as the euro sign or curly quotes make it fail with "Couldn't open this data". Re-save the file as CSV UTF-8 to fix that.
How do I profile nested JSON or JSONL files?
Drop the file on rows.page. Nested objects become dot-path columns like user.address.city, up to 8 levels deep and 200 child fields per object, so every nested field gets its own card in the Columns panel. Arrays stay as list columns that show the first 6 items and "+N".