Tarang Deep Goel

Off the clock · Mar 2025

CSV data visualizer

A CSV visualiser that runs on my laptop, my first real project built with coding tools and models. Upload a CSV, its columns are sorted into dimensions and metrics, then filter, aggregate, chart and download, with saved views per file.

csv-data-visualizer · Project3

How do I pull data in the least amount of time and get the most out of it?

This started when I was looking at website data from MPL. We were running experiments across different states in India - does vernacularization improve conversion? We used to track and monitor 50+ landing pages every day across multiple campaigns and channels. MPL was heavy on BigQuery, and website data was a challenge to track since we were using Google Analytics, which had its own schema for data storage, and there was no easy way to track installs since ours was a web-to-app journey - the ad led to the website and the app install happened via a downloaded APK, since RMG apps were not allowed on the Play Store. There was no easy way to track installs from a page since no attribution survived an APK install. Analyst bandwidth was at a premium - asking for data was a week's turnaround, with some just straight up saying they couldn't work with Google's schema and to continue using the GA UI (we also had the UA to GA4 migration going on, which was a significant schema and tracking change), and a Tableau dashboard was a distant dream.

A lot of the data I dealt with came as a CSV - mostly from querying BigQuery, and the first question is usually a simple one: what's the average, how many installs per state, how is it against the traffic, what is the conversion? The next step used to be a deeper set of questions - the drill downs. If this is the average, what is dragging the average down? As soon as you want to change the dimensions of the data you have to query again, download the CSV, and start with the analysis again - this was especially painful when you were fighting with marketing every day on why the installs are down.

This was the time when coding models were just getting off the ground - Claude 3.7 Sonnet had just launched and Cursor was what everyone was talking about. This was also my first time trying out how good the models are and how I can start using them to make repetitive things easier for me.

For context, this was the spring of 2025. Andrej Karpathy had coined "vibe coding" only a month earlier, Claude Code was a research preview, and a model writing a whole React frontend and a FastAPI backend from a description was new. Today that's simply how things get built, but back then it was the bleeding edge, and this was my first project and foray into vibe coding.

I started off with a thought - if I had the most granular data that I usually needed to get to, I would be able to aggregate it to the number that we want to track and look at every day. This solves 2 problems for me - I am able to look at the headline and I am also able to look at what is resulting in my headline dropping, assuming I have the breakdowns in all the reasonable parameters.

The way to do it was Excel and pivot tables - have a huge sheet that has millions of rows (we are looking at downloads across 100+ pages, 100s of campaigns, 10s of channels, 10s of states and possibly more that I don't remember right now) but that made the Excel experience painful, and as far as I saw there was no way to actually have charts on pivot tables.

Coming off from a basic background in Python and data analysis, I knew the solution to my problem was pandas and numpy. But how do I write the code for this and how do I make it work? This is where the LLMs enter the picture but that is too far in the future of this project.

What I started off with was a basic charting and CSV management app that runs on my laptop, with the models writing most of the code for me: a data visualiser for CSVs with every filter and chart built by hand. Getting there took two repos.

How it works

flowchart TD
    A["Me, in the browser"] -->|"upload a CSV"| B["Saved on disk"]
    B --> C["Columns sorted into dimensions and metrics"]
    C -->|"I fix wrong guesses, pick the x-axis"| D["Column setup"]
    A -->|"value and range filters"| E["Filtered rows"]
    D --> E
    E --> F["Grouped by my dimensions, metrics summed"]
    F --> G["Table"]
    F --> H["Line chart, one line per metric"]
    F --> I["Filtered CSV download"]
    F -->|"save a view"| J["Saved view: filters + snapshot of the data"]
    J --> A

The whole thing rests on the thought from the start: if I have the most granular data, every number I care about is just that data grouped and added up. So the app sees every CSV as two kinds of columns. Dimensions are what I slice by: page, campaign, channel, state, date. Metrics are what I add up: visits, installs. When I upload a file it guesses which is which - anything numeric is a metric, everything else is a dimension - and I can correct it, since a numeric campaign ID is really a dimension. One dimension is the index, which becomes the x-axis of every chart.

Aggregation is a pivot table without Excel. I pick the dimensions I want to see the data by and the metrics I want, and the backend groups the rows by those dimensions and sums each metric. Every metric is summed, which is right for installs and visits but not for a conversion rate, which has to be worked out from the summed numbers instead. The first repo had formulas for exactly that, and they didn't make it into the restart.

Filtering is where most of the work went. A dimension gets a value filter, where I tick the states or campaigns I want, and a metric gets a range filter with a minimum and a maximum. The filters cascade: each one only offers the values that still exist after the others are applied, so I don't end up picking a combination that returns nothing.

The chart follows from the same numbers: a line chart with the index along the bottom and one line per metric. Comparing came out of filtering. If a value filter has more than one value ticked, say three states, the chart stops adding them together and draws one line per state for each metric.

Whatever is on screen can be downloaded as a CSV, and later a view could be saved: the filters plus a snapshot of the filtered data, kept per file so each upload has its own history. Everything lives as files on disk next to the app - the data, a small description of its columns and the snapshots - with no database, and every click reads the CSV again and redoes the work from scratch.

How I got here

1. A dashboard platform (March 2025)

The oldest piece is Project3, a "Data Visualization Platform" built around work data. It had a React frontend with charts and two FastAPI backends side by side, the newer one adding aggregation, formulas for derived metrics and live updates, with a proper test suite. There was no model anywhere in it. I picked dimensions and metrics, it aggregated and it drew, and its to-do list was about date filters, dark mode and a redesign based on India's COVID website.

Two days after that first commit I started again in a new, much smaller repo with one backend and none of the formulas or live updates.

2. The CSV visualiser (March to April 2025)

This is the part of the story that matters most to me. It was my first actual project where the models wrote the code for something I wanted to use and build myself: a React frontend with Material UI and Recharts for the charts, and a FastAPI backend doing the pandas work. I described what I wanted and the models wrote it.

So csv-data-visualizer began with just upload, a look at the columns and a download, one file at a time. Over the next week it grew an understanding of the data. It sorted columns into dimensions and metrics on its own (numbers are metrics, everything else is a dimension, and I could override it), then got filtering and aggregation, value and range filters, and cascading filters so each one only offered values that still existed. In early April it learned to compare: picking more than one value of a dimension gave one chart series per value.

That was when I realised every kind of question was a feature. "Compare A and B" needed its own branch in the backend and its own chart mode, and the tool could only ever answer the questions I had already built a control for.

3. The COVID dashboard look (May 2025)

I spent a lot of time on how it looked. The reference in my head was covid19india.org, the crowdsourced dashboard that half the country checked every day in 2020. A redesign based on India's COVID website was already on the to-do list of the very first repo in March, and in early May it finally landed: the app moved to that site's dark navy background and Archia font, and the chart series took its colours, the red, blue, green and grey that meant confirmed, active, recovered and deceased, plus a few more for when there were more metrics than that. Until then the charts had used the chart library's default palette.

4. Files and saved views (May 2025)

Late May was about keeping state. I wanted to save a view, meaning the filters plus a snapshot of the filtered data, come back to it later, and keep several files each with its own history, so I built that. Docker and deployment configs for Railway, Vercel and AWS followed, and that was the end of this repo. Two ideas from here come back later: several files with their own history, and versions of the data I can go back to.

That was the last of it. When I came back to the same problem in January 2026 it was with a different idea, letting a model write the analysis instead of me building a control for every question, and that became the AI data analyst.