Preparing a CSV for LLM Analysis Without Sending 100,000 Rows

The instinct with a dataset and a language model is to paste the data in and ask a question. That works up to a few hundred rows and then fails β€” expensively, and often silently, with confidently wrong arithmetic. This page covers the three strategies that scale and the formatting details that cut token cost by more than half.

Pick the right strategy first

Under ~200 rows β€” send the data. 200 to a few thousand β€” aggregate or sample. Beyond that β€” send the schema plus sample rows and have the model write code that runs against the real dataset. The last option scales without limit and is usually more accurate as well.

What a row actually costs

Row shapeTokens per row1,000 rows
5 short columns~1010,000
10 mixed columns~2222,000
10 columns with long decimals~4040,000
10 columns, free text field~8080,000
Same data as JSON~4545,000

Two things stand out. JSON roughly doubles the cost for identical data, because every key is repeated on every row. And unrounded decimals roughly double it again β€” a detail nobody thinks about that can be the largest single line in the bill.

Formatting that halves the cost

Round every number

// Tokenisation splits long numbers unpredictably 1234.56789012 β†’ roughly 8 tokens 1234.57 β†’ roughly 4 tokens 1235 β†’ roughly 2 tokens // Over 10,000 numeric cells that is tens of thousands // of tokens spent on precision nobody reads.

Round to the precision the analysis actually needs β€” usually two decimal places, often zero. If the question is "which region grew fastest", trailing digits are pure cost.

Drop columns you are not asking about

The most effective single reduction, and the easiest to overlook. A table exported from a system typically carries internal IDs, audit timestamps, foreign keys and status flags that have nothing to do with the question.

// 18 columns as exported id,uuid,created_at,updated_at,created_by,tenant_id, region,product,quantity,unit_price,currency,discount_pct, total,status,sync_state,version,archived,notes // 5 columns the question needs region,product,quantity,total,status // ~70% fewer tokens, and better focus.

Normalise dates and shorten headers

// Verbose transaction_created_timestamp,customer_geographic_region 2026-08-01T14:30:00.000Z,Europe Middle East and Africa // Compact β€” the header row is sent once, the data every row date,region 2026-08-01,EMEA

Header names are cheap because they appear once. Values are expensive because they repeat. Truncating a timestamp to a date, and using codes instead of full names, pays off on every row.

βœ… Declare units and codes once

Rather than repeating currency symbols or units in every cell, state them in a short preamble:

Sales by region, FY2026. All amounts in GBP thousands. Regions: EMEA (Europe/Middle East/Africa), APAC (Asia Pacific), AMER (Americas). Status: A=active, C=cancelled, P=pending. region,month,revenue,status EMEA,01,4210,A

The preamble costs perhaps 60 tokens once and removes repeated text from every row. It also removes ambiguity the model would otherwise have to guess at.

Sampling

When the model needs to see representative data rather than all of it, which rows you pick matters more than how many.

StrategyGood forRisk
First N rowsUnderstanding structureSorted data gives a badly skewed view
Head + tailSpotting range and trendMisses the middle entirely
RandomEstimating distributionsMisses rare but important categories
StratifiedMost analysisNeeds a sensible strata column
Edge casesData quality reviewNot representative β€” say so

⚠️ First-N sampling on sorted data is actively misleading

Exports are usually sorted β€” by date, by ID, by region. Taking the first 100 rows of a date-sorted export gives the model only the oldest records, and it will describe a pattern that reflects the sort order rather than the data.

Worse, nothing signals the problem. The model produces a confident summary of an unrepresentative slice. Always shuffle or stratify unless you specifically want the earliest rows.

# Stratified: proportional representation of each group import pandas as pd sample = (df.groupby('region', group_keys=False) .apply(lambda g: g.sample(min(len(g), 20), random_state=0))) # Plus the extremes, which are often the interesting part edges = pd.concat([df.nlargest(5, 'total'), df.nsmallest(5, 'total')]) payload = pd.concat([sample, edges]).drop_duplicates()

Whatever you send, tell the model it is a sample and how it was drawn. Otherwise it will treat 100 rows as the population and describe them as such.

Aggregating first

Often the model does not need rows at all β€” it needs the answer to a question about the rows, plus context to interpret it.

// Instead of 50,000 transaction rows Summary of 50,000 transactions, Jan–Jun 2026. Amounts in GBP thousands. region,month,txn_count,revenue,avg_order,refund_rate EMEA,01,4210,1842,0.44,0.021 EMEA,02,4380,1955,0.45,0.019 APAC,01,2110,701,0.33,0.038 ... // 18 rows instead of 50,000, and the model can now // reason about trends rather than attempt arithmetic.

This plays to the actual division of strengths. Computing a sum over 50,000 rows is trivial for code and error-prone for a model. Noticing that APAC's refund rate is double EMEA's and suggesting why is the reverse.

The approach that scales without limit

For anything beyond a few thousand rows, send the schema and a sample, and have the model write code that runs against the full dataset.

Dataset: transactions.csv, 2.4M rows. Columns: date DATE transaction date region TEXT EMEA | APAC | AMER product_id TEXT e.g. "SKU-4471" quantity INT units, always >= 1 unit_price DECIMAL GBP, excludes VAT status TEXT A=active | C=cancelled | P=pending Sample rows: date,region,product_id,quantity,unit_price,status 2026-01-04,EMEA,SKU-4471,3,24.99,A 2026-01-04,APAC,SKU-1180,1,149.00,C Notes: cancelled rows should be excluded from revenue. ~2% of rows have a null region. Write a pandas query answering: which products had the largest month-on-month revenue growth in Q2?
Send the dataSend schema, model writes code
Row limitA few hundredUnlimited
Token costScales with rowsConstant
Arithmetic accuracyUnreliableExact
ReproducibleNoYes β€” you keep the query
AuditableNoYes β€” you can read it
Data leaves your systemYesNo β€” only the schema

That last row matters for anything sensitive. Sending a schema and two anonymised sample rows exposes far less than uploading a customer transaction table, and it is frequently the difference between a workflow that passes review and one that does not.

πŸ’‘ Include the data quirks in the schema

Nulls, sentinel values, duplicate keys, mixed date formats, the fact that cancelled rows must be excluded β€” these are what make generated queries wrong. Stating them in the prompt costs a few tokens and prevents the most common class of error.

A useful habit: keep a short data-notes file alongside each dataset and paste it in. It is documentation you needed anyway.

Fix the data before sending it

ProblemEffectFix
BOM at file startFirst column header unmatchedStrip ο»Ώ
Mixed date formatsModel guesses inconsistentlyNormalise to ISO
Empty vs "N/A" vs "null"Treated as different valuesPick one and say so
Commas inside fieldsColumn misalignmentQuote properly, or use TSV
Newlines inside fieldsRows splitEscape or strip
Trailing whitespaceCategories fail to groupTrim every value
Inconsistent casingEMEA β‰  emeaNormalise
Duplicate headersAmbiguous referencesRename

The BOM is worth singling out because it is invisible and Excel adds it by default. A parser reads the first header as ο»Ώregion rather than region, so that one column silently fails to match while every other column works β€” which looks like a data problem rather than an encoding one.

🚨 Check what Excel did to your export

If the CSV passed through Excel, assume leading zeros are gone, long identifiers have been rounded to 15 significant digits, and anything resembling a date has been converted. None of it errors and none of it is visible in the file.

Export from the source system directly where you can. Where you cannot, compare a few identifier columns against the original before analysing anything.

Wide versus long

// Wide β€” compact, good for comparison across periods region,jan,feb,mar,apr EMEA,4210,4380,4510,4390 APAC,2110,2240,2180,2350 // Long β€” verbose, good for filtering and grouping region,month,revenue EMEA,jan,4210 EMEA,feb,4380 ...

Wide is more token-efficient and reads better when the question compares across columns β€” "which region declined in April?". Long is better when the question filters or groups, and is the format aggregation code expects.

For sending to a model, wide usually wins: fewer tokens, and the visual layout makes period-over-period comparison easy to see.

Converting between CSV and JSON?

Convert either direction in your browser, with no type guessing and no upload β€” which matters when the data is customer records.

Open the CSV to JSON Converter β†’

Summary

  • Under 200 rows send data; beyond a few thousand send schema and let the model write code.
  • CSV costs about half what JSON does for the same table.
  • Round every number. Long decimals can double the token count.
  • Drop irrelevant columns β€” usually the single biggest reduction.
  • Declare units and codes once in a preamble rather than repeating them per row.
  • Never sample the first N rows of sorted data, and always say a sample is a sample.
  • Aggregate before sending. Models reason about trends; code computes totals.
  • Check for a BOM and Excel damage before trusting the file.

Frequently Asked Questions

How many rows of CSV can I send to an LLM?

As a rough guide, a row of 10 short columns costs 15 to 25 tokens, so 1,000 rows is around 20,000 tokens. That fits most context windows but is expensive and dilutes attention. Beyond a few hundred rows you should be aggregating, sampling, or letting the model write code against the full dataset instead.

What is the best way to analyse a large dataset with an LLM?

Send the schema and a handful of sample rows, then have the model write code β€” SQL or pandas β€” that runs against the full data. The model is good at expressing what to compute and poor at being a spreadsheet, so give it the describing job and let ordinary code do the arithmetic.

Does rounding numbers really reduce token usage?

Substantially. Long decimals fragment into several tokens each, so 1234.56789012 can cost twice what 1234.57 costs. Across a table with thousands of numeric cells that is a large share of the total, and the extra precision is almost never used in the analysis.

Should I send CSV or JSON to an LLM?

CSV for anything tabular. JSON repeats every key on every row, so it costs roughly twice as much for identical data and the gap widens with row count. Use JSON only when the data is genuinely nested and cannot be expressed as a flat table.

How do I stop the model miscounting rows or totals?

Do not ask it to count or total anything. Arithmetic over many rows is exactly the task language models are worst at and ordinary code is perfect at. Compute the aggregates yourself and send the results, or have the model write the query and run it separately.

P

Written by Paras

We build free, browser-based file tools and write the reference material we wish existed when we were looking things up. Spotted an error? Tell us and we will fix it.