CSV Profiler

Drop a CSV in and read back what is actually in it, one column at a time: the type it really holds, how much of it is blank, how many distinct values there are, where the numbers sit, and which values come up most. The report appears on the page, the copy button takes it whole, and the download is the same profile as JSON. Nothing leaves this tab.

Want to fix what the profile turns up? Open the app

Reading the file before you trust it

A CSV tells you its column names and nothing else. Everything that matters comes from the rows, and scrolling a few hundred of them tells you almost nothing about the other 90,000. These are the moments people go looking for a profiler:

  • A file arrived from someone else. A vendor export, a client extract, a download from an admin panel. Before you write any logic against it you want to know which columns are half empty, which ones hold one repeated value, and whether the identifier column is actually unique.
  • A load is about to run. Warehouse loaders fail on the row where a column that looked numeric turns out to hold "N/A". A profile names the type of every column up front, so the surprise happens on your screen rather than at 3 a.m. in a scheduler.
  • A join is about to happen. Distinct counts on both key columns settle whether the relationship is one to one or one to many before you find out from a row count that tripled.
  • A migration finished. Profile the export from the old system and the export from the new one, then compare the two reports. Missing percentages and distinct counts that moved are the columns that lost something.
  • Someone asked what is in the file. The report is aligned plain text on purpose. It pastes into a ticket, an email or a pull request and still lines up.

The closest familiar thing is pandas.describe(), without the Python. No install, no notebook, no read_csv call that has to be told the dtypes first. Search for a CSV profiler and most of what comes back is a product page for an enterprise data-quality suite or documentation for a library you would have to install. This is the report, in a tab, in a second.

Worked example: seven rows you can check by hand

Here is orders.csv, small enough that every number below can be verified on paper:

order_id,customer,ordered_on,amount,paid
1001,Ada Lovelace,2024-01-15,120.5,true
1002,Grace Hopper,2024-01-17,80,true
1003,Alan Turing,2024-02-02,,false
1004,Ada Lovelace,2024-02-11,240.75,true
1005,Katherine Johnson,2024-03-04,60.25,false
1006,,2024-03-19,15,true
1007,Grace Hopper,2024-03-28,310,true

The report opens with the shape of the file and the duplicate check:

Profile of orders.csv
7 rows x 5 columns
duplicate rows: 0

Then a block per column. Here is amount, the numeric one:

amount
  type            numeric
  missing         1 of 7 (14.3%)
  distinct        6
  min / max       15 / 310
  mean            137.75
  median          100.25
  p25 / p75       65.1875 / 210.6875

Six values are filled and one is blank, so the mean is 826.5 divided by 6. The percentiles are interpolated rather than rounded to a neighbour: with six values sorted to 15, 60.25, 80, 120.5, 240.75, 310, the p25 position is 1.25, which sits a quarter of the way from 60.25 to 80 and gives 65.1875. The median lands at position 2.5 and comes out at 100.25, halfway between 80 and 120.5. No value in the file is 100.25, and that is the point.

And here is customer, which is text:

customer
  type            text
  missing         1 of 7 (14.3%)
  distinct        4
  length          11 to 17
  top values
    Ada Lovelace       2
    Grace Hopper       2
    Alan Turing        1
    Katherine Johnson  1

A text column reports the shortest and longest filled value in characters and then names the five most common values with their counts. Here there are only four distinct values, so all four are listed. Ties are broken alphabetically, which is why Ada comes before Grace at a count of two each.

The other three columns get the same treatment. order_id comes back numeric, spanning 1,001 to 1,007. ordered_on is typed as a date because every filled value is a real YYYY-MM-DD. paid is boolean, with true appearing five times and false twice. Above the report the widget lists the headline findings, which for this file is two lines: 7 rows x 5 columns and 2 columns with blanks: customer, amount.

How the report is put together

One pass over the file, then one block per column. The whole sequence runs in your tab:

  • The separator is worked out from the content. Comma, semicolon, tab and pipe are all recognised, so a European export written with semicolons profiles correctly without being told. The invisible byte-order mark that Excel writes at the front of a file is stripped before the first column name is read.
  • Header cells are made usable. A blank header becomes column_3 after its position, and a name that appears twice becomes name and name_2. Both renamings are reported as warnings rather than done quietly, because a duplicate header is usually a real defect in the file.
  • A type is decided per column, from every row. Not from a sample of the first hundred. A column is numeric only when every filled value in it reads as a number, boolean when every filled value is the word true or false in any casing, and a date when every filled value is a full ISO YYYY-MM-DD, optionally with a time on the end. Everything else is text, which is why a yes/no column is reported as text with two top values rather than as a boolean.
  • Missing means empty. A cell with nothing in it counts as missing. A cell holding NULL, N/A or - does not, because those are strings someone chose and the profile shows them to you in the top values list instead of guessing what they meant.
  • Distinct counts are exact. Every filled value goes into a hash set, so a distinct count of 9,870 in a 10,000 row column is a fact rather than an estimate. Case and whitespace are part of the value, so Berlin and Berlin are two distinct values, which is exactly what a downstream join will think too.
  • Duplicate rows are whole-row duplicates. Every cell has to match, in order. The count sits under the file shape at the top of the report.
  • Past a million values the report says so. Distinct counts and percentiles come from the first 1,000,000 values of a column and print a note saying that. The duplicate check is skipped and the line reads duplicate rows: not checked (over 1,000,000 rows). Nothing is quietly estimated and presented as exact.

Gotchas worth knowing

  • A money column written with trailing zeros stays text, and still gets statistics. The type test is a round trip: a value counts as a number only if turning it into one and back gives the same string. 120.5 passes and 120.50 does not, which is the rule that stops the ZIP code 01730 from becoming 1730 and a 17-digit account number from turning into scientific notation. So a column of 120.50 and 80.00 is text. The profile asks a second, looser question of every text column: do the values read as numbers? When at least 90% of the filled values do, the block gets min, max, mean, median and the quartiles as well as the lengths and top values, and the type line reads text (numeric-looking). A numbers line above the statistics says how many values were counted and how many did not parse, so a column with three N/A in it tells you the mean skipped three values. Below 90% nothing changes and the column is reported as plain text. This is the profiler only: the type stays text, and the converters, the pivot and the app still read the column as text.
  • Only ISO dates are recognised as dates. 2024-03-19 is a date. 3/4/25 stays text, because nobody can say whether that is March 4th or April 3rd, and a profile that picked one would be inventing a fact. Ambiguous dates show up in the top values list where you can see the ambiguity for yourself.
  • Ragged rows are padded, not rejected. A row with fewer fields than the widest row is filled out with empty cells and the count of padded rows appears as a warning. That keeps the profile running on a broken file, but it does inflate the missing counts on the trailing columns. If the warning fires, run the file through the CSV validator to see which lines are short.
  • Whole numbers print with thousands separators, decimals do not. A min of 1001 shows as 1,001 while a p25 of 1002.5 shows as 1002.5. If you are pasting figures into a spreadsheet, that comma is a display convention in the report and is not in the JSON download.
  • Long values are cut in the top values list. A value over 40 characters is shortened in the report so the counts stay in a readable column. The full value is in the JSON download.
  • There is no standard deviation. Min, max, mean, median and the two quartiles cover the shape of a column for the purpose this page serves. If you need moments, take the JSON and compute them.
  • 100 MB is where this page stops. Under that there is no row limit, no sign-up and no daily allowance. Past it the widget points you at the full editor, which streams the file instead of holding all of it in memory.

Frequently Asked Questions

What does the profiler measure for each column?

A type (numeric, boolean, date or text), how many values are missing and what share of the column that is, and how many distinct values it holds. Numeric columns then get min, max, mean, median and the 25th and 75th percentiles. Every other column gets the shortest and longest value length plus the five most common values with their counts. A text column whose filled values are at least 90% numbers, which is what a money column written 120.50 looks like, gets both blocks. Above all of that the report prints the row and column count and how many rows are exact duplicates of another row.

Why is my money column typed as text?

Because at least one value does not survive a round trip through a number. A column is called numeric only when every filled value reads back as the same string it started as, which rules out 120.50, 0.10 and 007. That rule exists so an invoice number with a leading zero is never turned into 7 and a ZIP code never loses its first digit. It does not cost you the statistics any more. When at least 90% of a text column's filled values read as numbers, the profile computes min, max, mean, median and the quartiles for them, prints the type as text (numeric-looking), and says how many values did not parse and were left out. The type stays text, so nothing downstream converts the column.

Which percentile method does it use?

Linear interpolation on the sorted values, which is what numpy and most spreadsheets do by default. The position is p times (n minus 1), and where that lands between two neighbours the answer is weighted between them. Six values of 15, 60.25, 80, 120.5, 240.75 and 310 give a p25 of 65.1875 rather than snapping to 60.25 or 80.

What happens on a file with more than a million rows?

It still runs, and it says which numbers stopped being exact. Distinct counts and percentiles are computed on the first 1,000,000 values of a column and the report prints "in the first 1,000,000 values" beside them. The duplicate row check is skipped entirely and the line reads "duplicate rows: not checked (over 1,000,000 rows)". Counts, missing values, min, max and mean are exact whatever the size, because none of them need to hold the column in memory.

What do the copy button and the download give me?

Two different things on purpose. Copy puts the plain-text report on the clipboard exactly as it appears on the page, which is the thing people paste into a ticket or a pull request. Download gives you the same profile as JSON, named after your file, so another tool can read it. The report is aligned with spaces, so it survives being pasted into a monospace block.

Does my file get uploaded anywhere?

No. There is no upload endpoint on this page. The file is read by JavaScript running in your tab, profiled there, and forgotten when you close it. That also means the ceiling is your browser rather than a plan: files up to 100 MB are handled here, and anything larger is better opened in the full editor, which streams instead of loading everything at once.

Is this the same as pandas describe?

It covers the same ground without the Python. describe() gives you count, mean, standard deviation, min, max and the quartiles for numeric columns and leaves object columns to a second call. This page reports the quartiles and the mean, skips standard deviation, and adds the things people actually open a file to check: what share of each column is blank, how many distinct values it holds, the most common values, and how many rows are duplicates.

Find out what is in the file

Free, no account, no upload, no daily allowance. Drop the CSV in, read the report, copy it or take the JSON.

Back to the profiler