<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Pranaav Karthik]]></title><description><![CDATA[Pranaav Karthik]]></description><link>https://pranaavkarthik.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 12 Nov 2026 07:24:38 GMT</lastBuildDate><atom:link href="https://pranaavkarthik.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[From Learning Programming to Building Real Projects: My Student Developer Journey]]></title><description><![CDATA[Most "developer journey" posts are written by people who have already made it. This one is not. I am a college student in a computer applications field…]]></description><link>https://pranaavkarthik.hashnode.dev/student-developer-journey-learning-to-building-projects</link><guid isPermaLink="true">https://pranaavkarthik.hashnode.dev/student-developer-journey-learning-to-building-projects</guid><category><![CDATA[Career]]></category><category><![CDATA[programming]]></category><category><![CDATA[Students]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Pranaav Karthik]]></dc:creator><pubDate>Wed, 11 Nov 2026 05:10:03 GMT</pubDate><content:encoded><![CDATA[<p>Most "developer journey" posts are written by people who have already made it. This one is not. I am a college student in a computer applications field, still at the beginner-to-intermediate stage, and I want to write down where I honestly am.</p>
<p>No big success story here. No job, no internship, no client work. Just what I have learned, what I studied recently, and what I plan to do next.</p>
<h2>What I know right now</h2>
<p>My toolbox is small and I would rather list it plainly:</p>
<ul>
<li>Python</li>
<li>C++</li>
<li>HTML and CSS</li>
<li>Microsoft Excel</li>
</ul>
<p>That is it. I am not going to pad the list with tools I have only heard of.</p>
<h2>Four different ways of thinking</h2>
<p>Each of these taught me something different, even though I am still early in all of them.</p>
<p><strong>Python</strong> reads close to plain English, so you spend more time on the problem than on the syntax. It also has Pandas for data work and Streamlit for quick web apps, which turned out to matter a lot (more on that below).</p>
<p><strong>C++</strong> is stricter. You have to think about types and how things are stored. It feels slower to write at first, but it makes you more careful. If you are deciding between the two, I have a separate post on that: <a href="https://pranaavkarthik.hashnode.dev/python-vs-cpp-what-to-learn-first">Python vs C++: What Should a College Student Learn First?</a>.</p>
<p><strong>HTML and CSS</strong> are not programming languages in the usual sense, but they teach you something the others do not. You write a few lines and see a result in the browser right away. That quick feedback is useful when you are learning.</p>
<p><strong>Excel</strong> is easy to underrate. In my assignments I work with formulas, where you change an input and every dependent calculation updates by itself. That idea, a result that follows from inputs, shows up everywhere in programming too. Dashboards are basically that idea with nicer visuals. I wrote about the practical side here: <a href="https://pranaavkarthik.hashnode.dev/excel-formulas-for-data-analysis">Microsoft Excel for Data Analysis: Useful Formulas and Practical Examples</a>.</p>
<h2>The gap between learning and building</h2>
<p>Here is the honest part. Knowing a language and building something with it are two separate skills.</p>
<p>When you learn syntax, every exercise is small and the answer is known. A real project is different. You have to decide how to load data, how to clean it, how to organise files, and how to show the result. Nobody gives you the steps in order.</p>
<p>So before attempting a project of my own, I wanted to see what a finished one looks like on the inside.</p>
<h2>Studying a real project</h2>
<p>So I picked a finished open-source project and went through it properly: a Sales Analytics Dashboard built with Python, Pandas and Streamlit. It was created by Asha Kalavagunta, not by me. Her original repo is at <a href="https://github.com/Ashakalavagunta/sales-analytics-dashboard">github.com/Ashakalavagunta/sales-analytics-dashboard</a>. I keep a copy on my own GitHub so I can read and run it.</p>
<p>I want to be clear about that. Credit matters, and I would rather say "I studied this" than pretend it is mine.</p>
<p>What did reading it teach me? A few things I could not easily get from short exercises:</p>
<ul>
<li><strong>Separate your concerns.</strong> The cleaning script and the app are different files. The ETL script cleans the data once, and the app only worries about showing it.</li>
<li><strong>Check your own data.</strong> The ETL script ends with <code>assert</code> checks, for example that sales are positive and there are no duplicate rows. I had not thought of doing that before.</li>
<li><strong>One filtered DataFrame can drive everything.</strong> Six sidebar filters feed a single mask, and every KPI and chart reads from the result.</li>
<li><strong>Cache expensive work.</strong> Streamlit reruns the script on every click, so loading data needs caching.</li>
</ul>
<p>I wrote up the full walkthrough here: <a href="https://pranaavkarthik.hashnode.dev/python-sales-analytics-dashboard-streamlit-pandas">What I Learned Studying a Python Sales Analytics Dashboard (Streamlit + Pandas)</a>. If you want the Pandas basics behind it, start with <a href="https://pranaavkarthik.hashnode.dev/pandas-for-beginners-cleaning-analysing-data">Pandas for Beginners: Cleaning and Analysing Real-World Data</a>.</p>
<h2>Why I am not stopping at studying</h2>
<p>Reading code teaches you a lot, but it is a passive skill. I can follow a <code>groupby</code> when someone else wrote it. Writing one from a blank file, with my own question and my own messy dataset, is harder, and that is exactly why it is worth doing.</p>
<p>I also think there is a line between learning from a project and presenting it as your own work. For a portfolio, that line matters. A repo that is clearly yours, even if it is simple, says more than a polished project you did not write. I go into this in <a href="https://pranaavkarthik.hashnode.dev/github-portfolio-for-students">How College Students Can Build a Strong GitHub Portfolio</a>.</p>
<h2>What I plan to do next</h2>
<p>Nothing fancy, and nothing promised with dates:</p>
<ol>
<li><strong>Build my own project.</strong> Probably something in Python with a dataset I pick myself, using what I learned from studying Asha's work.</li>
<li><strong>Try the things I would change.</strong> While reading the dashboard, I noted a few experiments, like computing insight text from the data instead of typing it in, and splitting one big script into smaller files.</li>
<li><strong>Use GitHub properly.</strong> A clear README, a clean structure, and commits that make sense. <a href="https://pranaavkarthik.hashnode.dev/github-for-beginners-local-project-to-repository">Git and GitHub for College Students: From Local Project to GitHub Repository</a></li>
<li><strong>Look at certifications.</strong> I am interested in free, verifiable ones from providers like AWS, Google, Microsoft and Cisco. I have not completed any yet, so I will not claim any. If I finish one, I will say so here with proof.</li>
</ol>
<h2>If you are in the same place</h2>
<p>If you are a student who has learned some syntax and feels stuck before the project stage, here is what I would suggest, based on what I am doing myself:</p>
<ul>
<li>Pick one finished project and read it slowly. Run it. Change one thing and see what breaks.</li>
<li>Give credit to whoever wrote it, in your notes and in anything you publish.</li>
<li>Start your own small project soon, even if it feels too simple.</li>
</ul>
<p>That is the whole plan. It is not impressive yet, but it is real.</p>
<p>I will keep posting as I go. You can follow my work on GitHub at <a href="https://github.com/Pranaav717">github.com/Pranaav717</a>, and follow this blog for the next posts. If you have built something small as a student, I would like to hear what you made first.</p>
]]></content:encoded></item><item><title><![CDATA[How College Students Can Build a Strong GitHub Portfolio]]></title><description><![CDATA[Most student GitHub profiles look the same. A few repos named test, project1 or untitled, no descriptions, and a README that says nothing. Anyone who opens…]]></description><link>https://pranaavkarthik.hashnode.dev/github-portfolio-for-students</link><guid isPermaLink="true">https://pranaavkarthik.hashnode.dev/github-portfolio-for-students</guid><category><![CDATA[GitHub]]></category><category><![CDATA[Career]]></category><category><![CDATA[Students]]></category><category><![CDATA[programming]]></category><dc:creator><![CDATA[Pranaav Karthik]]></dc:creator><pubDate>Wed, 11 Nov 2026 05:08:36 GMT</pubDate><content:encoded><![CDATA[<p>Most student GitHub profiles look the same. A few repos named <code>test</code>, <code>project1</code> or <code>untitled</code>, no descriptions, and a README that says nothing. Anyone who opens that profile learns almost nothing about you.</p>
<p>A portfolio does not need to be big. It needs to be clear. Here is what I think makes a student profile worth opening, and one lesson I took from studying someone else's project.</p>
<h2>Why GitHub counts as a portfolio</h2>
<p>You are a student, so you probably do not have work experience to list. Your repos fill that gap. They show that you can finish something, explain it, and put it where others can run it.</p>
<p>I am not saying a profile gets you a job. I am saying it gives someone something concrete to look at instead of just a line on a resume.</p>
<h2>Pick projects that solve something</h2>
<p>Tutorials teach you tools. Projects show you can use them. Some ideas that are small enough for a college semester:</p>
<ul>
<li><strong>A tracker for something you actually use.</strong> Expenses, attendance, gym log, study hours.</li>
<li><strong>A data project on a public dataset.</strong> Clean it with Pandas, answer a few questions, show charts.</li>
<li><strong>A small web page or tool.</strong> A unit converter, a quiz app, a timetable generator.</li>
<li><strong>A script that saves you time.</strong> Renaming files, merging Excel sheets, scraping a notice board (check the site's rules first).</li>
<li><strong>A C++ or Python version of a classic.</strong> A bank system, a library manager. Fine for fundamentals, but add something of your own so it is not identical to every other one.</li>
</ul>
<p>Rules of thumb:</p>
<ol>
<li>Choose something you can finish in a few weeks.</li>
<li>Choose something you can explain in two sentences.</li>
<li>Prefer one finished project over five half-done ones.</li>
</ol>
<p>If you are unsure where to start, <a href="https://pranaavkarthik.hashnode.dev/build-interactive-dashboard-python-streamlit">How to Build an Interactive Dashboard Using Python and Streamlit</a> builds a small interactive dashboard step by step.</p>
<h2>Keep each repo tidy</h2>
<p>A messy repo hides good work. Here is a simple layout for a small Python project:</p>
<pre><code class="language-text">expense-tracker/
├── README.md
├── requirements.txt
├── .gitignore
├── app.py
├── src/
│   ├── __init__.py
│   └── tracker.py
├── data/
│   └── sample_expenses.csv
└── screenshots/
    ├── home.png
    └── report.png
</code></pre>
<p>What each piece is for:</p>
<ul>
<li><code>README.md</code> is the front page of your repo.</li>
<li><code>requirements.txt</code> lists the libraries so others can install them.</li>
<li><code>.gitignore</code> keeps junk like <code>__pycache__/</code> and <code>.env</code> out. See <a href="https://pranaavkarthik.hashnode.dev/github-for-beginners-local-project-to-repository">Git and GitHub for College Students: From Local Project to GitHub Repository</a> for the basics.</li>
<li><code>data/</code> holds a small sample file, not a huge private dataset.</li>
<li><code>screenshots/</code> holds the images your README shows.</li>
</ul>
<p>Name repos in plain words: <code>expense-tracker</code>, not <code>project_final_v2</code>. Fill in the one-line description box on GitHub too. It shows up in search and on your profile.</p>
<h2>Write a README people can use</h2>
<p>The README is the part most visitors read, and often the only part. Assume the reader is in a hurry and has never seen your project.</p>
<p>Include:</p>
<ol>
<li><strong>What it is</strong>, in one or two sentences.</li>
<li><strong>A screenshot or GIF.</strong></li>
<li><strong>Features</strong>, as a short list. Only list what actually works.</li>
<li><strong>Tech used.</strong></li>
<li><strong>How to install and run it.</strong> Commands they can copy.</li>
<li><strong>What you would improve.</strong> Honest and short.</li>
<li><strong>Credits and licence</strong>, if you used anyone's code or data.</li>
</ol>
<p>Here is an example you can copy and adapt. The project name and features are made up for illustration.</p>
<pre><code class="language-markdown"># Expense Tracker

A small command-line and Streamlit app to log daily expenses and see
monthly totals by category.

![Home screen](screenshots/home.png)

## Features
- Add, edit and delete expenses
- Monthly total per category
- Export to CSV

## Tech
Python 3.10, Pandas, Streamlit

## Run it locally
```bash
git clone https://github.com/your-username/expense-tracker.git
cd expense-tracker
python -m venv .venv
source .venv/bin/activate      # Windows: .venv\Scripts\activate
pip install -r requirements.txt
streamlit run app.py
```

## Sample data
`data/sample_expenses.csv` has 50 made-up rows so you can try it.

## What I would improve
- Add tests
- Support multiple currencies

## Credits
Category icons from [source]. Dataset structure inspired by [link].
</code></pre>
<p>Test your install steps on a clean folder, or ask a friend to try them. If the steps fail on someone else's laptop, the project looks broken however good the code is.</p>
<h2>Screenshots do a lot of work</h2>
<p>Most people will not clone your repo to see what it does. A screenshot answers that in two seconds.</p>
<ul>
<li>Take screenshots at normal window size, with sample data (never real personal data).</li>
<li>Save them in a <code>screenshots/</code> folder and link them with relative paths.</li>
<li>For apps with several pages, show two or three of the best, not twelve.</li>
<li>A short GIF of the main action is even better. Free tools like ScreenToGif (Windows) or Peek (Linux) can record one.</li>
</ul>
<h2>Host it if you can</h2>
<p>A live link beats a screenshot. If your project is a web page, GitHub Pages is free. If it is a Streamlit app, Streamlit Community Cloud can run it from your repo. <a href="https://pranaavkarthik.hashnode.dev/deploy-streamlit-app-community-cloud">How to Deploy a Python Streamlit Project Online (Free, Step by Step)</a> walks through that.</p>
<p>Put the live link near the top of the README. If a project cannot be hosted, like a desktop or C++ program, a short demo GIF does the same job.</p>
<h2>Pin and describe your profile</h2>
<p>Your profile page is the front door:</p>
<ul>
<li>Pin your best 3 to 6 repos.</li>
<li>Add a profile photo and a short bio: what you study, what you are learning.</li>
<li>Create a repo named exactly the same as your username. Its README shows up on your profile. A few lines about you and links to your best work is plenty.</li>
<li>Commit regularly. You do not need a green square every day, but a profile that went quiet two years ago looks abandoned.</li>
</ul>
<h2>Studying other people's projects, honestly</h2>
<p>This part is where I want to be straight with you.</p>
<p>Learning from other people's code is normal. Reading a good project is one of the fastest ways to see how a real app is organised. I did this with an open-source Superstore sales dashboard by <a href="https://github.com/Ashakalavagunta/sales-analytics-dashboard">Asha Kalavagunta</a>. It is built with Python, Pandas and Streamlit, and I went through its code to understand how the data cleaning, charts and filters fit together. <a href="https://pranaavkarthik.hashnode.dev/python-sales-analytics-dashboard-streamlit-pandas">What I Learned Studying a Python Sales Analytics Dashboard (Streamlit + Pandas)</a></p>
<p>What I took from it was a clear lesson about my own portfolio. A copy of someone else's project on my GitHub does not show what I can build. It shows that I found a good project. Those are different things, and a recruiter or teacher can tell the difference in about a minute.</p>
<p>So here is how I think it should be done:</p>
<ol>
<li><strong>Fork or copy it openly.</strong> Use GitHub's Fork button. It keeps a visible link back to the original, so nobody has to guess where it came from.</li>
<li><strong>Keep the original credit.</strong> Leave the licence and the author's name in place. Check the repo's licence for what you are allowed to do. If there is no licence, ask the author before reusing the code.</li>
<li><strong>Say what it is in your README.</strong> Something like "Forked from X. I studied this to learn Y." Do not describe it as something you made.</li>
<li><strong>Keep it out of your pinned repos</strong> if it is not your work, or label it clearly as a study fork.</li>
<li><strong>Then build your own.</strong> Take what you learned and make a different project. Same techniques, new idea, your own code.</li>
</ol>
<p>This is not about feeling bad. Everyone starts by reading other people's work. The only real mistake is putting that work on your profile as if you wrote it. It is easy to avoid and it keeps your profile believable.</p>
<p>For me, the next step is simple: use what I learned about Pandas, Streamlit and project structure to start projects of my own, and put those up front. <a href="https://pranaavkarthik.hashnode.dev/student-developer-journey-learning-to-building-projects">From Learning Programming to Building Real Projects: My Student Developer Journey</a></p>
<h2>A quick checklist before you share your profile</h2>
<ul>
<li><input disabled type="checkbox" /> Every pinned repo has a description</li>
<li><input disabled type="checkbox" /> Every pinned repo has a README with a screenshot</li>
<li><input disabled type="checkbox" /> Install steps tested on a fresh folder</li>
<li><input disabled type="checkbox" /> No passwords, API keys or <code>.env</code> files committed</li>
<li><input disabled type="checkbox" /> Borrowed code is credited, forks are labelled</li>
<li><input disabled type="checkbox" /> Repo names are readable</li>
<li><input disabled type="checkbox" /> Profile bio filled in</li>
</ul>
<h2>Wrap-up</h2>
<p>A strong student portfolio is a few finished projects that you can explain, with READMEs that someone can follow in five minutes. Pick something useful, keep the repo tidy, add a screenshot, host it if you can, and be clear about which work is yours.</p>
<p>You can see my profile at <a href="https://github.com/Pranaav717">github.com/Pranaav717</a>. It is a work in progress, and I will be adding my own projects there.</p>
]]></content:encoded></item><item><title><![CDATA[Microsoft Excel for Data Analysis: Useful Formulas and Practical Examples]]></title><description><![CDATA[If you are a student, there is a good chance your next assignment needs Excel, and a handful of formulas will cover most of it. This post uses one small…]]></description><link>https://pranaavkarthik.hashnode.dev/excel-formulas-for-data-analysis</link><guid isPermaLink="true">https://pranaavkarthik.hashnode.dev/excel-formulas-for-data-analysis</guid><category><![CDATA[excel]]></category><category><![CDATA[data analytics]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[Students]]></category><category><![CDATA[Beginner Developers]]></category><dc:creator><![CDATA[Pranaav Karthik]]></dc:creator><pubDate>Wed, 11 Nov 2026 05:07:08 GMT</pubDate><content:encoded><![CDATA[<p>If you are a student, there is a good chance your next assignment needs Excel, and a handful of formulas will cover most of it.</p>
<p>This post uses one small sales table and walks through the formulas I find most useful. I have checked every result below by recalculating it separately in Python, so the numbers match what Excel should show.</p>
<p>I have also done assignments where the sheet is formula-driven: you change an input and everything else updates by itself. That is near the end.</p>
<h2>The sample data</h2>
<p>Paste this into a blank sheet starting at cell A1.</p>
<table>
<thead>
<tr>
<th></th>
<th>A</th>
<th>B</th>
<th>C</th>
<th>D</th>
<th>E</th>
<th>F</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>Order ID</td>
<td>Product</td>
<td>Region</td>
<td>Units</td>
<td>Unit Price</td>
<td>Revenue</td>
</tr>
<tr>
<td>2</td>
<td>101</td>
<td>Pen</td>
<td>North</td>
<td>10</td>
<td>5</td>
<td>50</td>
</tr>
<tr>
<td>3</td>
<td>102</td>
<td>Pen</td>
<td>South</td>
<td>20</td>
<td>5</td>
<td>100</td>
</tr>
<tr>
<td>4</td>
<td>103</td>
<td>Book</td>
<td>North</td>
<td>5</td>
<td>40</td>
<td>200</td>
</tr>
<tr>
<td>5</td>
<td>104</td>
<td>Book</td>
<td>South</td>
<td>8</td>
<td>40</td>
<td>320</td>
</tr>
<tr>
<td>6</td>
<td>105</td>
<td>Bag</td>
<td>North</td>
<td>3</td>
<td>250</td>
<td>750</td>
</tr>
<tr>
<td>7</td>
<td>106</td>
<td>Bag</td>
<td>East</td>
<td>4</td>
<td>250</td>
<td>1000</td>
</tr>
<tr>
<td>8</td>
<td>107</td>
<td>Pen</td>
<td>East</td>
<td>15</td>
<td>5</td>
<td>75</td>
</tr>
<tr>
<td>9</td>
<td>108</td>
<td>Book</td>
<td>East</td>
<td>6</td>
<td>40</td>
<td>240</td>
</tr>
</tbody></table>
<p>The Revenue column is not typed in. It is a formula. In <code>F2</code>, enter:</p>
<pre><code class="language-excel">=D2*E2
</code></pre>
<p>Then drag the fill handle (the small square at the bottom-right of the cell) down to <code>F9</code>. Each row calculates its own revenue.</p>
<h2>The basic five</h2>
<h3>SUM</h3>
<p>Adds up numbers.</p>
<pre><code class="language-excel">=SUM(F2:F9)
</code></pre>
<p>Result: <strong>2735</strong>. That is total revenue. </p>
<h3>AVERAGE</h3>
<pre><code class="language-excel">=AVERAGE(F2:F9)
</code></pre>
<p>Result: <strong>341.875</strong>. Average revenue per order.</p>
<h3>COUNT and COUNTA</h3>
<p>These look alike but count different things.</p>
<pre><code class="language-excel">=COUNT(D2:D9)
=COUNTA(B2:B9)
</code></pre>
<p><code>COUNT</code> counts cells that contain <strong>numbers</strong>. <code>COUNTA</code> counts cells that are <strong>not empty</strong>, including text. Both return <strong>8</strong> here. Try <code>=COUNT(B2:B9)</code> and you get <strong>0</strong>, because the Product column is all text. That mistake is easy to make.</p>
<h2>IF: make the sheet decide</h2>
<p><code>IF</code> checks a condition and returns one thing if true, another if false.</p>
<p>In <code>G2</code>, add a header "Tier" in <code>G1</code>, then:</p>
<pre><code class="language-excel">=IF(F2&gt;=300,"High","Low")
</code></pre>
<p>Fill down. Orders 104, 105, and 106 (revenue 320, 750, 1000) show <strong>High</strong>. The other five show <strong>Low</strong>.</p>
<p>The pattern is <code>IF(test, value_if_true, value_if_false)</code>. </p>
<h2>SUMIF and COUNTIF: totals with a condition</h2>
<p>This is where Excel starts to feel like analysis.</p>
<pre><code class="language-excel">=SUMIF(C2:C9,"North",F2:F9)
</code></pre>
<p>Result: <strong>1000</strong>. It looks at the Region column, picks rows equal to "North", and adds their Revenue. The North orders are 50 + 200 + 750.</p>
<p>The order of arguments is: range to check, criteria, range to add.</p>
<pre><code class="language-excel">=SUMIF(B2:B9,"Pen",F2:F9)
</code></pre>
<p>Result: <strong>225</strong> (50 + 100 + 75).</p>
<p><code>COUNTIF</code> counts rows that meet a condition:</p>
<pre><code class="language-excel">=COUNTIF(C2:C9,"North")
=COUNTIF(F2:F9,"&gt;=300")
</code></pre>
<p>The first returns <strong>3</strong> orders from North. The second returns <strong>3</strong> orders with revenue of at least 300.</p>
<p>If you need more than one condition, look at <code>SUMIFS</code> and <code>COUNTIFS</code>. For example, <code>=SUMIFS(F2:F9,B2:B9,"Book",C2:C9,"East")</code> returns <strong>240</strong>.</p>
<h2>Looking things up</h2>
<p>Say you want the revenue of order 104 without scanning the table by eye. Put 104 in a cell, for example <code>K2</code>, and use a lookup.</p>
<h3>VLOOKUP</h3>
<pre><code class="language-excel">=VLOOKUP(K2,A2:F9,6,FALSE)
</code></pre>
<p>Result: <strong>320</strong>. Breaking it down:</p>
<ul>
<li><code>K2</code> is what you are looking for.</li>
<li><code>A2:F9</code> is the table. The lookup value must be in the <strong>first column</strong> of this range.</li>
<li><code>6</code> means "return the value from the 6th column of the range".</li>
<li><code>FALSE</code> means exact match. Use this almost every time. Leaving it out can give wrong answers on unsorted data.</li>
</ul>
<p>The weak point: that <code>6</code> is hard-coded. If someone inserts a column in the middle, the formula may return the wrong thing without any warning.</p>
<h3>XLOOKUP</h3>
<pre><code class="language-excel">=XLOOKUP(K2,A2:A9,F2:F9)
</code></pre>
<p>Result: <strong>320</strong>. You give it the column to search and the column to return. No column numbers, and exact match is the default.</p>
<p>One catch: XLOOKUP is available in <strong>Excel 2021 and Microsoft 365</strong> (plus Excel for the web). It will not work in Excel 2019 or older. If your college lab machines run an older version, use the next option.</p>
<h3>INDEX + MATCH</h3>
<pre><code class="language-excel">=INDEX(F2:F9,MATCH(K2,A2:A9,0))
</code></pre>
<p>Result: <strong>320</strong>. <code>MATCH</code> finds the position of 104 in the Order ID column (position 4). <code>INDEX</code> then returns the 4th item from the Revenue column. The <code>0</code> means exact match.</p>
<p>This works in every Excel version, and it can look to the left, which VLOOKUP cannot. For instance, to find the first order ID for "Bag":</p>
<pre><code class="language-excel">=INDEX(A2:A9,MATCH("Bag",B2:B9,0))
</code></pre>
<p>Result: <strong>105</strong>. It returns the first match only, so if a product appears many times you get the earliest row.</p>
<table>
<thead>
<tr>
<th>Function</th>
<th>Works in older Excel</th>
<th>Looks left</th>
<th>Column numbers needed</th>
</tr>
</thead>
<tbody><tr>
<td>VLOOKUP</td>
<td>Yes</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr>
<td>XLOOKUP</td>
<td>No (2021 / 365 only)</td>
<td>Yes</td>
<td>No</td>
</tr>
<tr>
<td>INDEX + MATCH</td>
<td>Yes</td>
<td>Yes</td>
<td>No</td>
</tr>
</tbody></table>
<h2>Relative vs absolute references</h2>
<p>This is the topic that causes the most confusion, and it matters as soon as you fill formulas down.</p>
<p>A <strong>relative</strong> reference like <code>F2</code> changes when you copy the formula. Copy it down one row and it becomes <code>F3</code>. That is what you want for <code>=D2*E2</code>.</p>
<p>An <strong>absolute</strong> reference uses dollar signs and stays fixed: <code>$F$11</code>.</p>
<p>Say you put the grand total in <code>F11</code>:</p>
<pre><code class="language-excel">=SUM(F2:F9)
</code></pre>
<p>Now you want each order's share of the total. In <code>H2</code>:</p>
<pre><code class="language-excel">=F2/$F$11
</code></pre>
<p>Fill it down. <code>F2</code> shifts to <code>F3</code>, <code>F4</code> and so on, but <code>$F$11</code> stays pinned to the total. Without the dollar signs, the second row would divide by <code>F12</code>, an empty cell, and you would see a <code>#DIV/0!</code> error.</p>
<p>For order 101 the share is 50 / 2735, about <strong>1.8%</strong>. For order 106 it is 1000 / 2735, about <strong>36.6%</strong>. Format column H as a percentage.</p>
<p>A quick way to add the dollar signs: click inside the reference in the formula bar and press <code>F4</code>. It cycles through <code>$F$11</code>, <code>F$11</code>, <code>$F11</code>, and <code>F11</code>.</p>
<h2>Formula-driven sheets: change an input, everything updates</h2>
<p>The real value of Excel is that you do not retype results. You set inputs once, and formulas react.</p>
<p>Extend the same sheet with a bonus rate. In <code>J4</code> type the label "Bonus rate", and in <code>K4</code> type <code>5%</code>. Then add a "Bonus" header in <code>I1</code> and, in <code>I2</code>:</p>
<pre><code class="language-excel">=F2*$K$4
</code></pre>
<p>Fill down to <code>I9</code>. The <code>$K$4</code> is absolute, so every row points at the same rate cell. Put a total at the bottom of the column:</p>
<pre><code class="language-excel">=SUM(I2:I9)
</code></pre>
<p>At 5%, order 106 gets a bonus of <strong>50</strong> and the total bonus is <strong>136.75</strong>. Now change <code>K4</code> to <code>10%</code>. Every bonus doubles without you touching a formula: order 106 becomes <strong>100</strong> and the total becomes <strong>273.5</strong>.</p>
<p>Try changing a price too. Set <code>E2</code> (the Pen price on order 101) from 5 to 6. Revenue for that row goes from 50 to <strong>60</strong>, the grand total in <code>F11</code> moves from 2735 to <strong>2745</strong>, and <code>=SUMIF(B2:B9,"Pen",F2:F9)</code> moves from 225 to <strong>235</strong>.</p>
<p>The habit to build: keep inputs in clearly labelled cells, never bury numbers inside formulas, and let everything else be calculated.</p>
<h2>Mistakes to avoid</h2>
<ul>
<li><strong>Numbers stored as text.</strong> If a number is left-aligned and <code>SUM</code> ignores it, it is probably text. Convert it or retype it.</li>
<li><strong>Forgetting <code>FALSE</code> or <code>0</code> in lookups.</strong> You may get a wrong match instead of an error, which is worse.</li>
<li><strong>Missing dollar signs.</strong> If a filled-down formula gives odd results from row three onward, check your references first.</li>
<li><strong>Typing results by hand.</strong> If a number can be a formula, make it one.</li>
<li><strong>Extra spaces in text.</strong> "North " is not equal to "North", and your <code>SUMIF</code> will quietly skip it.</li>
</ul>
<h2>Where to go next</h2>
<p>Once these feel normal, look at <code>SUMIFS</code>, <code>COUNTIFS</code>, <code>IFERROR</code>, and PivotTables.</p>
<p>Excel is also a good place to rehearse ideas before moving to Python. A <code>SUMIF</code> is the same thinking as a <code>groupby</code> in Pandas. If you want to see that side, I will link the Pandas post here: <a href="https://pranaavkarthik.hashnode.dev/pandas-for-beginners-cleaning-analysing-data">Pandas for Beginners: Cleaning and Analysing Real-World Data</a>.</p>
<p>If this helped, follow the blog for more practical student-level tutorials. You can also find my work on GitHub: <a href="https://github.com/Pranaav717">https://github.com/Pranaav717</a></p>
]]></content:encoded></item><item><title><![CDATA[Python vs C++: What Should a College Student Learn First?]]></title><description><![CDATA[Almost every first-year student asks the same thing: should I start with Python or C++? Your syllabus may have already decided for you. I use both as a…]]></description><link>https://pranaavkarthik.hashnode.dev/python-vs-cpp-what-to-learn-first</link><guid isPermaLink="true">https://pranaavkarthik.hashnode.dev/python-vs-cpp-what-to-learn-first</guid><category><![CDATA[Python]]></category><category><![CDATA[C++]]></category><category><![CDATA[programming]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Students]]></category><dc:creator><![CDATA[Pranaav Karthik]]></dc:creator><pubDate>Wed, 11 Nov 2026 05:05:11 GMT</pubDate><content:encoded><![CDATA[<p>Almost every first-year student asks the same thing: should I start with Python or C++? Your syllabus may have already decided for you.</p>
<p>I use both as a student, so this post is a comparison, not a verdict. I'll show the same small programs in each language and then tell you when I'd pick which.</p>
<p>All the code below was compiled with <code>g++ -std=c++17</code> or run with <code>python3</code>, so you can paste it and try.</p>
<h2>The short version</h2>
<table>
<thead>
<tr>
<th></th>
<th>Python</th>
<th>C++</th>
</tr>
</thead>
<tbody><tr>
<td>Typing</td>
<td>Dynamic (types checked at run time)</td>
<td>Static (types checked at compile time)</td>
</tr>
<tr>
<td>Runs how</td>
<td>Interpreted</td>
<td>Compiled to machine code</td>
</tr>
<tr>
<td>Memory</td>
<td>Handled for you</td>
<td>You control it (with help from the standard library)</td>
</tr>
<tr>
<td>Typical use</td>
<td>Data analysis, scripting, web backends, automation</td>
<td>Systems software, games, embedded, competitive programming</td>
</tr>
<tr>
<td>First-week feel</td>
<td>Gentle</td>
<td>Steeper</td>
</tr>
</tbody></table>
<p>Now the same programs, side by side.</p>
<h2>Hello world</h2>
<p>Python:</p>
<pre><code class="language-python">print("Hello, world!")
</code></pre>
<p>C++:</p>
<pre><code class="language-cpp">#include &lt;iostream&gt;

int main() {
    std::cout &lt;&lt; "Hello, world!" &lt;&lt; std::endl;
    return 0;
}
</code></pre>
<p>Both print <code>Hello, world!</code>. One line versus six. The C++ version isn't pointless, though. <code>#include</code> pulls in the input/output library, and <code>main</code> is where execution starts. Python just starts at the top of the file.</p>
<p>To run them:</p>
<pre><code class="language-bash">python3 hello.py

g++ -std=c++17 -o hello hello.cpp
./hello
</code></pre>
<p>Notice the extra compile step in C++. If you have a syntax error, the compiler stops you before anything runs.</p>
<h2>Loops</h2>
<p>Print the squares of 1 to 5.</p>
<p>Python:</p>
<pre><code class="language-python">for i in range(1, 6):
    print(i * i)
</code></pre>
<p>C++:</p>
<pre><code class="language-cpp">#include &lt;iostream&gt;

int main() {
    for (int i = 1; i &lt;= 5; i++) {
        std::cout &lt;&lt; i * i &lt;&lt; "\n";
    }
    return 0;
}
</code></pre>
<p>Output for both:</p>
<pre><code class="language-text">1
4
9
16
25
</code></pre>
<p>Python uses indentation to mark the loop body. C++ uses curly braces and semicolons. Python's <code>range(1, 6)</code> stops before 6, which trips up beginners. The C++ loop spells out start, condition, and step, so you see exactly what is happening. I find C++ loops more verbose but also more explicit.</p>
<h2>Reading input</h2>
<p>Python:</p>
<pre><code class="language-python">name = input("Your name: ")
age = int(input("Your age: "))
print(f"Hi {name}, next year you will be {age + 1}.")
</code></pre>
<p>C++:</p>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;string&gt;

int main() {
    std::string name;
    int age;
    std::cout &lt;&lt; "Your name: ";
    std::getline(std::cin, name);
    std::cout &lt;&lt; "Your age: ";
    std::cin &gt;&gt; age;
    std::cout &lt;&lt; "Hi " &lt;&lt; name &lt;&lt; ", next year you will be " &lt;&lt; age + 1 &lt;&lt; ".\n";
    return 0;
}
</code></pre>
<p>With the input <code>Asha</code> and <code>20</code>, both programs print <code>Hi Asha, next year you will be 21.</code></p>
<p>Two things to notice. In Python, <code>input()</code> always gives you text, so you convert with <code>int()</code>. In C++, you declare the type first and <code>cin &gt;&gt; age</code> reads straight into an <code>int</code>. Also, <code>getline</code> reads a whole line, spaces included. Using <code>cin &gt;&gt; name</code> would stop at the first space. This is one of those small C++ details that bites students early.</p>
<h2>A list (or vector) and a sum</h2>
<p>Python:</p>
<pre><code class="language-python">marks = [72, 85, 90, 64]
total = sum(marks)
print("Total:", total)
print("Average:", total / len(marks))
</code></pre>
<p>C++:</p>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;numeric&gt;
#include &lt;vector&gt;

int main() {
    std::vector&lt;int&gt; marks = {72, 85, 90, 64};
    int total = std::accumulate(marks.begin(), marks.end(), 0);
    std::cout &lt;&lt; "Total: " &lt;&lt; total &lt;&lt; "\n";
    std::cout &lt;&lt; "Average: " &lt;&lt; static_cast&lt;double&gt;(total) / marks.size() &lt;&lt; "\n";
    return 0;
}
</code></pre>
<p>Output for both:</p>
<pre><code class="language-text">Total: 311
Average: 77.75
</code></pre>
<p>A Python list can hold mixed types and grows freely. A C++ <code>std::vector&lt;int&gt;</code> holds only ints, but it also grows when you add items. The cast to <code>double</code> matters. Without it, C++ would do integer division and give you <code>77</code>. Python's <code>/</code> always gives a float, so you never think about it.</p>
<h2>A simple class</h2>
<p>Python:</p>
<pre><code class="language-python">class Student:
    def __init__(self, name, marks):
        self.name = name
        self.marks = marks

    def average(self):
        return sum(self.marks) / len(self.marks)

s = Student("Ravi", [72, 85, 90])
print(s.name, s.average())
</code></pre>
<p>C++:</p>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;numeric&gt;
#include &lt;string&gt;
#include &lt;vector&gt;

class Student {
public:
    std::string name;
    std::vector&lt;int&gt; marks;

    Student(std::string n, std::vector&lt;int&gt; m) : name(n), marks(m) {}

    double average() const {
        int total = std::accumulate(marks.begin(), marks.end(), 0);
        return static_cast&lt;double&gt;(total) / marks.size();
    }
};

int main() {
    Student s("Ravi", {72, 85, 90});
    std::cout &lt;&lt; s.name &lt;&lt; " " &lt;&lt; s.average() &lt;&lt; "\n";
    return 0;
}
</code></pre>
<p>Python prints <code>Ravi 82.33333333333333</code>. C++ prints <code>Ravi 82.3333</code>, because <code>cout</code> shows about six significant digits by default. Same value, different default formatting.</p>
<p>The C++ version teaches things Python hides: access specifiers (<code>public</code>), constructors with initialiser lists, and <code>const</code> methods that promise not to change the object. If your college covers OOP in C++, this is exactly the material you will see in exams.</p>
<h2>Learning curve</h2>
<p>Python is easier to start with. You can write something useful in your first hour and spend your energy on logic instead of syntax. Error messages are also friendlier.</p>
<p>C++ asks more up front. Types, headers, compilation, pointers, and references all arrive early. Error messages from the compiler can be long and confusing at first. But the struggle forces you to understand what the computer is doing.</p>
<h2>Performance</h2>
<p>Python is generally slower for heavy number crunching, because it is interpreted and every value carries extra bookkeeping. C++ compiles to machine code and gives you fine control, so it is the usual choice when speed or tight resource use really matters.</p>
<p>I am not going to quote benchmark numbers here, because they depend on the task, the machine, and how the code is written. The qualitative picture is what a beginner needs. Many popular Python libraries (NumPy, for example) do their heavy work in compiled code underneath, so Python is often fast enough in practice for data work.</p>
<h2>Memory management</h2>
<p>In Python, you create objects and the runtime cleans them up when nothing refers to them. You rarely think about memory.</p>
<p>In C++, you decide how long things live. Modern C++ makes this much safer than the old textbooks suggest: <code>std::vector</code> and <code>std::string</code> free their own memory, and smart pointers like <code>std::unique_ptr</code> handle manual allocations. Still, understanding stack versus heap and pointers is part of the language, and exam questions on it are common.</p>
<h2>Where each one gets used</h2>
<ul>
<li><strong>Python:</strong> data analysis, automation scripts, web backends, machine learning, quick prototypes.</li>
<li><strong>C++:</strong> game engines, operating systems and drivers, embedded software, performance-critical systems, and competitive programming (its standard library is popular there).</li>
</ul>
<p>Both are widely used.</p>
<h2>Fundamentals transfer</h2>
<p>Loops, conditions, functions, arrays, recursion, and classes mean the same thing in both. The first language matters less than actually finishing programs in it. Once you know one well, the second takes a fraction of the time.</p>
<h2>So which should you learn first?</h2>
<p>Here is how I would decide.</p>
<p><strong>Start with Python if:</strong></p>
<ul>
<li>You are completely new to programming.</li>
<li>You are interested in data, automation, or building small projects quickly.</li>
<li>You want early wins to stay motivated.</li>
</ul>
<p><strong>Start with C++ if:</strong></p>
<ul>
<li>Your syllabus or placement path uses it (many Indian college courses do).</li>
<li>You want to understand how memory and types work from the start.</li>
<li>You are into competitive programming or systems-level work.</li>
</ul>
<p><strong>Or do both:</strong> learn the basics in Python, then pick up C++ for a deeper view. There is no single correct order.</p>
<p>If your college has already put C++ on your timetable, don't fight it. Learn it properly, and use Python on the side for projects.</p>
<h2>Wrapping up</h2>
<p>Python gets you building faster. C++ shows you what is underneath. Pick the one that matches your next six months, not the one that wins internet arguments.</p>
<p>I will keep posting what I learn as a student, so follow along if that is useful. More on practical projects in <a href="https://pranaavkarthik.hashnode.dev/python-sales-analytics-dashboard-streamlit-pandas">What I Learned Studying a Python Sales Analytics Dashboard (Streamlit + Pandas)</a>, and on the tools I use for them in <a href="https://pranaavkarthik.hashnode.dev/pandas-for-beginners-cleaning-analysing-data">Pandas for Beginners: Cleaning and Analysing Real-World Data</a>.</p>
]]></content:encoded></item><item><title><![CDATA[How to Deploy a Python Streamlit Project Online (Free, Step by Step)]]></title><description><![CDATA[Your Streamlit app runs fine on your laptop. Then someone asks for the link, and all you can send is localhost:8501, which only works on your own machine…]]></description><link>https://pranaavkarthik.hashnode.dev/deploy-streamlit-app-community-cloud</link><guid isPermaLink="true">https://pranaavkarthik.hashnode.dev/deploy-streamlit-app-community-cloud</guid><category><![CDATA[Python]]></category><category><![CDATA[streamlit]]></category><category><![CDATA[deployment]]></category><category><![CDATA[GitHub]]></category><dc:creator><![CDATA[Pranaav Karthik]]></dc:creator><pubDate>Wed, 11 Nov 2026 05:03:26 GMT</pubDate><content:encoded><![CDATA[<p>Your Streamlit app runs fine on your laptop. Then someone asks for the link, and all you can send is <code>localhost:8501</code>, which only works on your own machine. That address points at your computer and nowhere else.</p>
<p>Streamlit Community Cloud is a free way to fix that. It pulls your code from GitHub, installs your packages and gives you a public URL. This guide walks through the whole thing, and then covers the errors you are most likely to hit.</p>
<p>I have checked the steps below against the official Streamlit docs. The Community Cloud interface changes now and then, so if a button has moved, the docs at docs.streamlit.io are the final word.</p>
<h2>Local vs deployed: what actually changes</h2>
<p>When you run <code>streamlit run app.py</code>, your app uses your Python, your installed packages and your files. Deployed, it runs on a fresh Linux machine that knows nothing about your laptop. Community Cloud builds a new Python environment from your repository, then starts your app inside it.</p>
<p>So the rule is simple: <strong>anything your app needs must be in the repo or listed in a dependency file.</strong> Almost every deployment error comes from breaking that rule.</p>
<h2>Before you deploy: prepare the project</h2>
<p>You need three things.</p>
<ol>
<li>A GitHub repository with your app in it. If you have not pushed your code yet, see <a href="https://pranaavkarthik.hashnode.dev/github-for-beginners-local-project-to-repository">Git and GitHub for College Students: From Local Project to GitHub Repository</a>.</li>
<li>A <code>requirements.txt</code> file.</li>
<li>Every data file the app reads, committed to the repo.</li>
</ol>
<p>A minimal layout looks like this:</p>
<pre><code class="language-text">my-dashboard/
├── app.py
├── sales.csv
├── requirements.txt
└── .gitignore
</code></pre>
<p>If you want a working app to practise on, <a href="https://pranaavkarthik.hashnode.dev/build-interactive-dashboard-python-streamlit">How to Build an Interactive Dashboard Using Python and Streamlit</a> gives you one that fits this layout exactly.</p>
<h3>Write requirements.txt</h3>
<p>Community Cloud checks for dependency files in this order: <code>uv.lock</code>, <code>Pipfile</code>, <code>environment.yml</code>, <code>requirements.txt</code>, then <code>pyproject.toml</code>. It looks next to your main file first, then in the repo root, and uses the first one it finds. Pick one and stick with it. For most student projects, that is <code>requirements.txt</code>.</p>
<pre><code class="language-text">streamlit&gt;=1.32.0
pandas&gt;=2.0.0
plotly&gt;=5.18.0
</code></pre>
<p>Each line works like an argument to <code>pip install</code>. A few rules from the docs:</p>
<ul>
<li>Leave out built-in modules like <code>math</code>, <code>json</code> and <code>random</code>. They ship with Python and listing them causes errors.</li>
<li>Streamlit is preinstalled, so you only need to list it if you want to pin or limit its version.</li>
<li>If there is no requirements file at all, the app runs with only Streamlit and its own dependencies. That is why a missing file shows up as <code>ModuleNotFoundError</code>.</li>
</ul>
<p>For a real-world reference, the open-source Superstore dashboard by Asha Kalavagunta (<a href="https://github.com/Ashakalavagunta/sales-analytics-dashboard">GitHub repo</a>) lists streamlit, pandas, numpy and plotly in its <code>requirements.txt</code>, and is deployed on Community Cloud. That is the same shape of file you need here.</p>
<p>To generate your own list from your environment, run <code>pip freeze</code>, but trim it. The full output includes every sub-dependency pinned to your exact versions, and that can cause conflicts on a different operating system. Writing only the packages you import is cleaner.</p>
<h3>Use paths that survive deployment</h3>
<p>The docs say the working directory on Community Cloud is always the root of your repository. If your app sits in the root, <code>pd.read_csv("sales.csv")</code> works. If it sits in a subfolder, it won't. A safer habit is to build the path from the script's own location:</p>
<pre><code class="language-python">from pathlib import Path
import pandas as pd

DATA_PATH = Path(__file__).parent / "sales.csv"
df = pd.read_csv(DATA_PATH)
</code></pre>
<p>Two more path rules: use forward slashes (<code>data/sales.csv</code>), because backslashes don't work, and match the file name's capital letters exactly. Windows ignores case, but Linux does not. <code>Sales.csv</code> and <code>sales.csv</code> are different files there.</p>
<h3>Keep secrets out of GitHub</h3>
<p>If your app uses an API key or password, never hard-code it and never commit it. Keep it in <code>.streamlit/secrets.toml</code> on your machine and add that path to <code>.gitignore</code>:</p>
<pre><code class="language-text">.streamlit/secrets.toml
venv/
__pycache__/
</code></pre>
<p>In your code, read values like this:</p>
<pre><code class="language-python">import streamlit as st

api_key = st.secrets["api_key"]
</code></pre>
<p>The docs are blunt about this: the <code>secrets.toml</code> file should never be committed to your repository. If you pushed a key by mistake, deleting the file in a later commit is not enough, because it stays in Git history. Treat the key as leaked and generate a new one.</p>
<h2>Deploy step by step</h2>
<p>Push your latest code to GitHub first. Then:</p>
<ol>
<li>Go to <strong>share.streamlit.io</strong> and sign in. You can sign in with GitHub, which also connects your repositories.</li>
<li>Click <strong>Create app</strong> in the top-right corner.</li>
<li>When asked if you already have an app, choose <strong>Yup, I have an app</strong>.</li>
<li>Fill in three fields: <strong>Repository</strong>, <strong>Branch</strong> and <strong>Main file path</strong>. The main file path is the file that starts your app, such as <code>app.py</code>. If it is inside a folder, write <code>folder/app.py</code>. You can also click <strong>Paste GitHub URL</strong> and drop in a link to the file.</li>
<li>Optional: choose a custom subdomain in the <strong>App URL</strong> field. Leave it blank and you get an automatically generated one. You can change it later.</li>
<li>Click <strong>Advanced settings</strong> if you need to set the Python version or add secrets (next two sections).</li>
<li>Click <strong>Deploy</strong>.</li>
</ol>
<p>The first build takes a few minutes, longer if you have many packages. Logs show up on the right side while it builds. Only people with write access to the repository can see them.</p>
<h3>Pick the Python version</h3>
<p>Under <strong>Advanced settings</strong> there is a <strong>Python version</strong> dropdown. The docs say the default is 3.12. Choose the same version you use locally where you can, because package versions and built-in behaviour differ between releases. You can check yours with <code>python --version</code>.</p>
<p>Also pick a version that still gets security updates. The docs warn that if your version becomes unsupported, it will be forcibly upgraded and may break your app.</p>
<h3>Add secrets</h3>
<p>In the same <strong>Advanced settings</strong> dialog, paste the contents of your local <code>secrets.toml</code> into the <strong>Secrets</strong> field and click <strong>Save</strong>. For an app that is already live, you can edit secrets later from the app's settings.</p>
<h3>Your live app</h3>
<p>When the build finishes, your app opens at its own URL. That link is public, so anyone you send it to can use it.</p>
<h2>Common errors and how to fix them</h2>
<p>Open the logs first. On a running app, click <strong>Manage app</strong> in the lower-right corner. Most fixes start with reading the last few lines of the log.</p>
<h3>ModuleNotFoundError: No module named 'plotly'</h3>
<p>The package is missing from your dependency file. Add it to <code>requirements.txt</code>, commit, and push. Community Cloud picks up the change and redeploys. Check spelling too: the install name and import name sometimes differ (<code>scikit-learn</code> installs, <code>sklearn</code> imports).</p>
<h3>FileNotFoundError for your CSV</h3>
<p>Three usual causes:</p>
<ul>
<li>The file never got committed. Run <code>git status</code> and check that it is tracked.</li>
<li>The path is wrong for a subfolder app. Use the <code>Path(__file__).parent</code> approach shown earlier.</li>
<li>The file name has different capitalisation on Linux.</li>
</ul>
<h3>Installer errors or version conflicts</h3>
<p>If the log shows a failure while installing, suspect a pinned version that doesn't exist for the Python version you chose. Loosen the pin (<code>pandas&gt;=2.0.0</code> instead of <code>pandas==2.0.1</code>), or try a different Python version under the app settings.</p>
<p>Some libraries also need system-level software, not just Python packages. For those, add a <code>packages.txt</code> file in the repo root, one package name per line. Community Cloud runs Debian Linux and installs them with <code>apt-get</code>. Most beginner apps never need this file.</p>
<h3>Two dependency files fighting each other</h3>
<p>Only the first dependency file found is used, in the order listed earlier. If you have both a <code>requirements.txt</code> and a <code>pyproject.toml</code>, one of them is being ignored. Keep one.</p>
<h3>The app works, then slows down or crashes</h3>
<p>Community Cloud gives each app limited resources. The docs list roughly 2.7 GB of memory and 2 CPU cores at the top end, with a lower floor, and note that these numbers are approximate as of February 2024 and may change without notice. Signs you are over the limit are a slow app or a message saying the app has gone over its resource limits.</p>
<p>What helps:</p>
<ul>
<li>Cache data loading with <code>@st.cache_data</code> so you don't re-read big files on every click.</li>
<li>Don't load a huge dataset into memory if you only need a few columns. <code>pd.read_csv(..., usecols=[...])</code> helps.</li>
<li>Open <strong>Manage app</strong> and choose reboot to clear memory.</li>
</ul>
<h3>Edits don't show up</h3>
<p>Community Cloud redeploys from GitHub, so a change only goes live after you push it. Commit, push, and refresh. Also check that you pushed to the same branch you selected during deployment. A <code>master</code> versus <code>main</code> mix-up is an easy way to push code the app never sees.</p>
<h3>"This app has gone to sleep"</h3>
<p>Free apps with no traffic for 12 hours go to sleep. Anyone who visits can click <strong>Yes, get this app back up!</strong> to wake it, and it takes a short while to start again. You do not need to be the owner to do this.</p>
<p>This matters if you are putting the link on a resume or portfolio. Open the app yourself before you share it, and tell people the first load might be slow. Don't rely on tricks to keep it awake. Just expect the delay.</p>
<h3>Other limits to know about</h3>
<p>Community Cloud limits updates from GitHub to five per minute, and all apps are hosted in the United States. If your <code>print()</code> output is missing from the logs, call <code>sys.stdout.flush()</code> after printing.</p>
<h2>A quick checklist before you click Deploy</h2>
<ul>
<li>App runs locally from the repo root with <code>streamlit run app.py</code></li>
<li><code>requirements.txt</code> lists every third-party import</li>
<li>Data files are committed and use relative paths</li>
<li><code>secrets.toml</code> is in <code>.gitignore</code></li>
<li>Latest code is pushed to the branch you will select</li>
</ul>
<h2>Wrap up</h2>
<p>Deployment is mostly housekeeping. Get your dependencies listed, keep your paths relative, keep secrets out of Git, then fill in the three fields on Community Cloud. When something breaks, the logs usually tell you exactly which file or package is missing.</p>
<p>Once your app is live, add the link to your README so people can find it. <a href="https://pranaavkarthik.hashnode.dev/github-portfolio-for-students">How College Students Can Build a Strong GitHub Portfolio</a> covers how to present projects properly.</p>
<p>If you want to see a full dashboard that is deployed on Community Cloud, read through <a href="https://github.com/Ashakalavagunta/sales-analytics-dashboard">Asha Kalavagunta's Sales Analytics Dashboard</a>. You can follow my own learning projects on <a href="https://github.com/Pranaav717">GitHub</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Git and GitHub for College Students: From Local Project to GitHub Repository]]></title><description><![CDATA[You wrote a project on your laptop. It works. Now you want it on GitHub so your teacher, a friend, or a recruiter can see it, and the first git push throws…]]></description><link>https://pranaavkarthik.hashnode.dev/github-for-beginners-local-project-to-repository</link><guid isPermaLink="true">https://pranaavkarthik.hashnode.dev/github-for-beginners-local-project-to-repository</guid><category><![CDATA[Git]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[programming]]></category><dc:creator><![CDATA[Pranaav Karthik]]></dc:creator><pubDate>Wed, 11 Nov 2026 05:01:47 GMT</pubDate><content:encoded><![CDATA[<p>You wrote a project on your laptop. It works. Now you want it on GitHub so your teacher, a friend, or a recruiter can see it, and the first <code>git push</code> throws an error you do not understand.</p>
<p>This post walks through the whole path, from an empty folder to a pushed repo, and then the errors that usually show up on the way. I ran every command below on a real machine (Git 2.43) against a local test remote, so the outputs are what Git actually printed.</p>
<h2>Git vs GitHub, in two lines</h2>
<p><strong>Git</strong> is a tool on your computer. It records snapshots of your project, called commits, so you can go back or see what changed.</p>
<p><strong>GitHub</strong> is a website that stores a copy of your Git project online. Git works fine without GitHub. GitHub is just where people put their repos to share them.</p>
<p>A <strong>repository</strong> (repo) is a project folder that Git is tracking.</p>
<h2>One-time setup</h2>
<p>Tell Git who you are. This name and email get attached to every commit.</p>
<pre><code class="language-bash">git config --global user.name "Your Name"
git config --global user.email "you@example.com"
</code></pre>
<p>Use the same email as your GitHub account if you want commits linked to your profile.</p>
<h2>Step 1: Turn a folder into a repo</h2>
<pre><code class="language-bash">mkdir my-project
cd my-project
git init
</code></pre>
<p>Output:</p>
<pre><code class="language-text">Initialized empty Git repository in .../my-project/.git/
</code></pre>
<p>On my machine Git also printed a long hint about the default branch name. That hint matters later, so keep it in mind.</p>
<p>Now add a file:</p>
<pre><code class="language-bash">echo 'print("hello git")' &gt; app.py
git status --short
</code></pre>
<pre><code class="language-text">?? app.py
</code></pre>
<p><code>??</code> means Git sees the file but is not tracking it yet.</p>
<h2>Step 2: Stage and commit</h2>
<p>Committing is two steps. First you <strong>stage</strong> the files you want in the snapshot. Then you <strong>commit</strong> them with a message.</p>
<pre><code class="language-bash">git add app.py
git commit -m "Add first script"
</code></pre>
<pre><code class="language-text">[master (root-commit) 8d064d0] Add first script
 1 file changed, 1 insertion(+)
 create mode 100644 app.py
</code></pre>
<p>Look at the first word inside the brackets: <code>master</code>. That is the branch Git created for me. Remember it.</p>
<p>A few habits that help:</p>
<ul>
<li>Write messages that say what changed. "Add first script" beats "update".</li>
<li>Commit small and often.</li>
<li>Run <code>git status</code> whenever you are unsure what state you are in.</li>
</ul>
<h2>Step 3: Create the repo on GitHub</h2>
<p>On github.com, click <strong>New repository</strong>. Give it a name, leave it empty (no README, no .gitignore, no license), and click <strong>Create</strong>. GitHub then shows you a URL like <code>https://github.com/your-username/my-project.git</code>.</p>
<p>Leaving it empty matters. If GitHub adds a README, your local repo and the online repo start with different histories and your first push will be rejected.</p>
<h2>Step 4: Connect local to remote</h2>
<p>A <strong>remote</strong> is just a saved link to another copy of your repo. By convention the main one is called <code>origin</code>.</p>
<pre><code class="language-bash">git remote add origin https://github.com/your-username/my-project.git
git remote -v
</code></pre>
<p>In my test I used a local bare repo as the remote, so my output looked like this:</p>
<pre><code class="language-text">origin	../remote.git (fetch)
origin	../remote.git (push)
</code></pre>
<p>With GitHub you will see your GitHub URL there instead. The behaviour is the same.</p>
<h2>Step 5: The push that fails (master vs main)</h2>
<p>GitHub's default branch is <code>main</code>. Depending on your Git version and settings, <code>git init</code> may still create <code>master</code>. So you run the push command from GitHub's instructions:</p>
<pre><code class="language-bash">git push -u origin main
</code></pre>
<p>And get this:</p>
<pre><code class="language-text">error: src refspec main does not match any
error: failed to push some refs to '../remote.git'
</code></pre>
<p>Read it slowly. It says: you asked me to push a branch called <code>main</code>, but I have no branch with that name. Your branch is <code>master</code>. Check:</p>
<pre><code class="language-bash">git branch
</code></pre>
<pre><code class="language-text">* master
</code></pre>
<p>There it is. The mismatch is just a name.</p>
<h3>Fix 1: rename your local branch to main</h3>
<p>This is what I would do.</p>
<pre><code class="language-bash">git branch -M main
git push -u origin main
</code></pre>
<pre><code class="language-text">To ../remote.git
 * [new branch]      main -&gt; main
branch 'main' set up to track 'origin/main'.
</code></pre>
<p><code>-M</code> renames the branch even if the name is taken. <code>-u</code> links your local <code>main</code> to <code>origin/main</code>, so later you can type plain <code>git push</code>.</p>
<h3>Fix 2: push master into a remote branch called main</h3>
<p>If you want to keep <code>master</code> locally:</p>
<pre><code class="language-bash">git push -u origin master:main
</code></pre>
<pre><code class="language-text">To ../remote2.git
 * [new branch]      master -&gt; main
branch 'master' set up to track 'origin/main'.
</code></pre>
<p>It works, but now your local <code>master</code> tracks a remote <code>main</code>. That is confusing a month later, so Fix 1 is cleaner.</p>
<h3>Stop it happening again</h3>
<p>Tell Git to name new repos <code>main</code> from the start:</p>
<pre><code class="language-bash">git config --global init.defaultBranch main
</code></pre>
<p>Git 2.28 or newer supports this setting.</p>
<h2>Step 6: Branches</h2>
<p>A <strong>branch</strong> is a separate line of work. You try something on a branch, and if it works you merge it back into <code>main</code>. If it does not, you delete the branch and nothing is harmed.</p>
<pre><code class="language-bash">git switch -c add-readme
echo "# My Project" &gt; README.md
git add README.md
git commit -m "Add README"
</code></pre>
<pre><code class="language-text">Switched to a new branch 'add-readme'
[add-readme 495fd55] Add README
 1 file changed, 1 insertion(+)
 create mode 100644 README.md
</code></pre>
<p>Go back to <code>main</code> and merge:</p>
<pre><code class="language-bash">git switch main
git merge add-readme
git push
git branch -d add-readme
</code></pre>
<pre><code class="language-text">Switched to branch 'main'
Your branch is up to date with 'origin/main'.
Updating 8d064d0..495fd55
Fast-forward
 README.md | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 README.md
To ../remote.git
   8d064d0..495fd55  main -&gt; main
Deleted branch add-readme (was 495fd55).
</code></pre>
<p>"Fast-forward" means <code>main</code> had nothing new of its own, so Git just moved the pointer forward. No extra merge commit.</p>
<p>For solo college projects you can commit straight to <code>main</code>. Branches become useful when you work in a group or want to experiment safely.</p>
<h2>Common errors after the first push</h2>
<h3>Rejected: fetch first / non-fast-forward</h3>
<p>You push and get:</p>
<pre><code class="language-text"> ! [rejected]        main -&gt; main (fetch first)
error: failed to push some refs to '../remote.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
</code></pre>
<p>The online repo has commits you do not have. Maybe a teammate pushed, maybe you edited a file on github.com, maybe you pushed from another laptop. Git refuses to overwrite them.</p>
<p>Fix: pull first, then push.</p>
<pre><code class="language-bash">git pull --no-rebase --no-edit
git push
</code></pre>
<pre><code class="language-text">Merge made by the 'ort' strategy.
 notes.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 notes.txt
To ../remote.git
   e92fe90..f6a0bb1  main -&gt; main
</code></pre>
<p>Please do not reach for <code>git push --force</code> to make the error go away. That overwrites the remote's commits, including your teammate's.</p>
<h3>Merge conflict</h3>
<p>If both sides changed the same lines, the pull stops:</p>
<pre><code class="language-text">CONFLICT (content): Merge conflict in app.py
Automatic merge failed; fix conflicts and then commit the result.
</code></pre>
<p>Open the file. Git marks both versions:</p>
<pre><code class="language-text">print("hello git")
# my edit
&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD
print('bye')
=======
# friend's line
&gt;&gt;&gt;&gt;&gt;&gt;&gt; 19ba3faf8afa0169000c7478eb89b9abf0822b1a
</code></pre>
<p>Everything between <code>&lt;&lt;&lt;&lt;&lt;&lt;&lt;</code> and <code>=======</code> is yours. Everything between <code>=======</code> and <code>&gt;&gt;&gt;&gt;&gt;&gt;&gt;</code> is theirs. Edit the file to the version you actually want, delete the marker lines, then:</p>
<pre><code class="language-bash">git add app.py
git commit -m "Merge friend's changes"
git push
</code></pre>
<pre><code class="language-text">To ../remote.git
   19ba3fa..f7a1f06  main -&gt; main
</code></pre>
<h3>Authentication failed</h3>
<p>Since August 2021, GitHub does not accept your account password for Git operations over HTTPS. If you type your password, you get an authentication error. Two ways around it:</p>
<p><strong>Option A: GitHub CLI.</strong> Install it from cli.github.com, then:</p>
<pre><code class="language-bash">gh auth login
</code></pre>
<p>Pick GitHub.com, HTTPS, and log in through the browser. After that, <code>git push</code> just works.</p>
<p><strong>Option B: Personal access token.</strong> On GitHub go to Settings, Developer settings, Personal access tokens, and generate one with the <code>repo</code> scope. When Git asks for a password, paste the token instead. Treat it like a password and never commit it into a file.</p>
<p>I could not run the login flows in my test setup because they need a browser, so follow GitHub's current docs for exact screens.</p>
<h3>Accidentally committed junk</h3>
<p>Things like <code>__pycache__/</code>, <code>.env</code> files, or virtual environments should not be in a repo. Create a file named <code>.gitignore</code> before your first commit:</p>
<pre><code class="language-text">__pycache__/
.venv/
.env
*.pyc
</code></pre>
<p>If a secret like an API key did get pushed, deleting it in a later commit is not enough. It stays in history. Revoke the key and make a new one.</p>
<h2>A quick command cheat sheet</h2>
<table>
<thead>
<tr>
<th>Command</th>
<th>What it does</th>
</tr>
</thead>
<tbody><tr>
<td><code>git init</code></td>
<td>Start tracking a folder</td>
</tr>
<tr>
<td><code>git status</code></td>
<td>See what changed</td>
</tr>
<tr>
<td><code>git add &lt;file&gt;</code></td>
<td>Stage a file</td>
</tr>
<tr>
<td><code>git commit -m "msg"</code></td>
<td>Save a snapshot</td>
</tr>
<tr>
<td><code>git remote add origin &lt;url&gt;</code></td>
<td>Link to GitHub</td>
</tr>
<tr>
<td><code>git push -u origin main</code></td>
<td>First push</td>
</tr>
<tr>
<td><code>git pull</code></td>
<td>Bring in remote changes</td>
</tr>
<tr>
<td><code>git switch -c &lt;name&gt;</code></td>
<td>Create and move to a branch</td>
</tr>
</tbody></table>
<h2>What a finished repo looks like</h2>
<p>If you want to see a real small project laid out on GitHub, look at <a href="https://github.com/Ashakalavagunta/sales-analytics-dashboard">Asha Kalavagunta's Sales Analytics Dashboard</a>. It is an open-source Streamlit project that I studied, and it is a good example of a repo with an app file, an ETL script, a requirements file and a README. My GitHub has a copy of it too, but the original is hers, so that is the link to open. <a href="https://pranaavkarthik.hashnode.dev/python-sales-analytics-dashboard-streamlit-pandas">What I Learned Studying a Python Sales Analytics Dashboard (Streamlit + Pandas)</a></p>
<h2>Wrap-up</h2>
<p>The path is short: <code>git init</code>, <code>git add</code>, <code>git commit</code>, <code>git remote add</code>, <code>git push</code>. Nearly every beginner error comes from three things: the branch is <code>master</code> but you pushed <code>main</code>, the remote has commits you lack, or you are logging in with a password instead of a token.</p>
<p>Once you can push, put your next project on GitHub from day one. <a href="https://pranaavkarthik.hashnode.dev/github-portfolio-for-students">How College Students Can Build a Strong GitHub Portfolio</a> covers how to make those repos look good. My profile is at <a href="https://github.com/Pranaav717">github.com/Pranaav717</a> if you want to follow along as I add projects.</p>
]]></content:encoded></item><item><title><![CDATA[Understanding ETL in Python Through a Practical Project]]></title><description><![CDATA[A dashboard is only as good as the data behind it. If dates are text and duplicates are still in the file, every chart is slightly wrong. ETL is the…]]></description><link>https://pranaavkarthik.hashnode.dev/etl-using-python-pandas-practical-project</link><guid isPermaLink="true">https://pranaavkarthik.hashnode.dev/etl-using-python-pandas-practical-project</guid><category><![CDATA[Python]]></category><category><![CDATA[ETL]]></category><category><![CDATA[pandas]]></category><category><![CDATA[data-engineering]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Pranaav Karthik]]></dc:creator><pubDate>Wed, 11 Nov 2026 04:59:50 GMT</pubDate><content:encoded><![CDATA[<p>A dashboard is only as good as the data behind it. If dates are text and duplicates are still in the file, every chart is slightly wrong.</p>
<p>ETL is the standard way to fix that before any charts exist. In this post I will explain what ETL means and build a small pipeline in Pandas, with real outputs from running it. Then I will show how an open-source dashboard follows the same stages.</p>
<p>If Pandas basics are new to you, read <a href="https://pranaavkarthik.hashnode.dev/pandas-for-beginners-cleaning-analysing-data">Pandas for Beginners: Cleaning and Analysing Real-World Data</a> first.</p>
<h2>What is ETL?</h2>
<p>ETL stands for Extract, Transform, Load.</p>
<ul>
<li><strong>Extract:</strong> read the raw data from its source (here, a CSV).</li>
<li><strong>Transform:</strong> clean it and add useful columns.</li>
<li><strong>Load:</strong> save the result somewhere the next step can use it (here, another CSV).</li>
</ul>
<p>That is all. The word "pipeline" just means these steps run in order, every time, the same way. Running one script is better than fixing a file by hand, because you can rerun it when new data arrives.</p>
<h2>The raw file</h2>
<p>I made <code>orders_raw.csv</code>, a small file with problems you will meet in real data: a missing sales and profit value, a duplicated order, a date that is not a date, and column names with spaces.</p>
<pre><code class="language-text">Order ID,Order Date,Ship Date,Region,Category,Sales,Profit,Discount
2001,01/05/2024,01/09/2024,West,Furniture,260.50,41.68,0.0
2002,01/09/2024,01/11/2024,East,Technology,640.00,128.00,0.1
2003,02/14/2024,02/20/2024,West,Office Supplies,18.20,-3.64,0.2
2004,02/20/2024,02/22/2024,Central,Technology,,,0.0
2005,03/02/2024,03/06/2024,East,Furniture,310.00,-31.00,0.3
2005,03/02/2024,03/06/2024,East,Furniture,310.00,-31.00,0.3
2006,03/18/2024,03/19/2024,South,Office Supplies,50.00,12.50,0.0
2007,04/01/2024,04/05/2024,West,Technology,420.00,63.00,0.15
2008,not a date,04/15/2024,Central,Furniture,150.00,9.00,0.1
2009,05/07/2024,05/10/2024,South,Technology,610.00,183.00,0.0
2010,07/21/2024,07/24/2024,East,Office Supplies,21.50,6.45,0.0
</code></pre>
<h2>Step 1: Extract</h2>
<pre><code class="language-python">import pandas as pd

RAW, OUT = "orders_raw.csv", "orders_clean.csv"

df = pd.read_csv(RAW, encoding="latin1")
print(df.shape)
</code></pre>
<pre><code class="language-text">(11, 8)
</code></pre>
<p>The <code>encoding="latin1"</code> part is worth explaining. Files exported from Excel sometimes contain characters that are not valid UTF-8, and <code>read_csv</code> fails with a decode error. <code>latin1</code> is a forgiving encoding that reads them. My sample file is plain ASCII so it does not need it, but real exports often do.</p>
<h2>Step 2: Transform, part one (cleaning)</h2>
<p>Clean before you calculate. If you build columns on dirty data, you carry the dirt forward.</p>
<pre><code class="language-python">df.columns = [c.strip().replace(" ", "_") for c in df.columns]

df["Order_Date"] = pd.to_datetime(df["Order_Date"], format="%m/%d/%Y", errors="coerce")
df["Ship_Date"] = pd.to_datetime(df["Ship_Date"], format="%m/%d/%Y", errors="coerce")

for col in ["Sales", "Profit", "Discount"]:
    df[col] = pd.to_numeric(df[col], errors="coerce")

before = len(df)
df = df.dropna(subset=["Sales", "Profit", "Order_Date"]).drop_duplicates()
print("rows removed:", before - len(df))
</code></pre>
<pre><code class="language-text">rows removed: 3
</code></pre>
<p>What happened here:</p>
<ul>
<li>Column names lost their spaces, so <code>Order Date</code> became <code>Order_Date</code>.</li>
<li><code>errors="coerce"</code> turns anything unparseable into a missing value (<code>NaT</code> for dates, <code>NaN</code> for numbers) instead of crashing. That is how "not a date" became a missing date, and then <code>dropna</code> removed it.</li>
<li>We also specify <code>format="%m/%d/%Y"</code>. Without it, Pandas guesses, and dates like 01/05/2024 can be read as 1 May instead of 5 January.</li>
</ul>
<p>Three rows went: order 2004 (missing sales and profit), order 2008 (bad date), and the second copy of order 2005.</p>
<h2>Step 3: Transform, part two (feature engineering)</h2>
<p>Feature engineering means creating new columns from existing ones, so analysis gets easier. We will add five.</p>
<pre><code class="language-python">df["Year"] = df["Order_Date"].dt.year
df["Quarter"] = "Q" + df["Order_Date"].dt.quarter.astype(str)
df["Days_to_Ship"] = (df["Ship_Date"] - df["Order_Date"]).dt.days.clip(lower=0)
df["Profit_Margin"] = (df["Profit"] / df["Sales"].replace(0, float("nan")) * 100).round(2)
df["Revenue_Band"] = pd.cut(
    df["Sales"],
    bins=[0, 50, 200, 500, df["Sales"].max() + 1],
    labels=["Low", "Medium", "High", "Premium"],
    right=False,
)
print(df[["Order_ID", "Sales", "Year", "Quarter", "Days_to_Ship", "Profit_Margin", "Revenue_Band"]])
</code></pre>
<pre><code class="language-text">    Order_ID  Sales  Year Quarter  Days_to_Ship  Profit_Margin Revenue_Band
0       2001  260.5  2024      Q1             4           16.0         High
1       2002  640.0  2024      Q1             2           20.0      Premium
2       2003   18.2  2024      Q1             6          -20.0          Low
4       2005  310.0  2024      Q1             4          -10.0         High
6       2006   50.0  2024      Q1             1           25.0       Medium
7       2007  420.0  2024      Q2             4           15.0         High
9       2009  610.0  2024      Q2             3           30.0      Premium
10      2010   21.5  2024      Q3             3           30.0          Low
</code></pre>
<p>A few notes on each one:</p>
<ul>
<li><strong>Year and Quarter</strong> come from the <code>.dt</code> accessor on a datetime column. You need real dates first, which is why cleaning came earlier.</li>
<li><strong>Days_to_Ship</strong> subtracts two dates to get a timedelta, and <code>.dt.days</code> turns it into a number. The <code>clip(lower=0)</code> stops negative values from data-entry mistakes.</li>
<li><strong>Profit_Margin</strong> is profit divided by sales, times 100. Replacing a zero sales value with NaN avoids a divide-by-zero.</li>
<li><strong>Revenue_Band</strong> uses <code>pd.cut</code>, which sorts numbers into bins. With <code>right=False</code>, each bin includes its left edge, so a sale of exactly 50 lands in Medium, not Low. Look at order 2006 in the output. It is an easy edge case to get wrong, so test it yourself.</li>
</ul>
<p>Check the bin behaviour in isolation:</p>
<pre><code class="language-python">print(pd.cut(pd.Series([49.99, 50, 200]), bins=[0, 50, 200, 500, 1000],
             labels=["L", "M", "H", "P"], right=False).tolist())
</code></pre>
<pre><code class="language-text">['L', 'M', 'H']
</code></pre>
<h2>Step 4: Validate</h2>
<p>Before saving, check your own work. <code>assert</code> stops the script if a condition is false.</p>
<pre><code class="language-python">assert df["Sales"].min() &gt; 0, "Sales has non-positive values"
assert df["Days_to_Ship"].min() &gt;= 0, "Negative ship days found"
assert df.duplicated().sum() == 0, "Duplicates still present"
assert df["Order_Date"].isnull().sum() == 0, "Null order dates found"
print("all checks passed")
</code></pre>
<pre><code class="language-text">all checks passed
</code></pre>
<p>This looks boring when it passes. It earns its place the day a new data file breaks something, because the script fails loudly instead of quietly saving bad data. One honest caveat: some of these checks can never fail given the earlier steps. For example, <code>clip(lower=0)</code> already guarantees no negative ship days. Good checks should be able to fail, so think about what could actually go wrong with your data.</p>
<h2>Step 5: Load</h2>
<pre><code class="language-python">df.to_csv(OUT, index=False)
print(pd.read_csv(OUT).shape)
print(df["Revenue_Band"].value_counts().sort_index())
</code></pre>
<pre><code class="language-text">(8, 13)
Revenue_Band
Low        2
Medium     1
High       3
Premium    2
Name: count, dtype: int64
</code></pre>
<p>We started with 11 rows and 8 columns. We ended with 8 rows and 13 columns. <code>index=False</code> stops Pandas writing the row numbers as an extra column.</p>
<p>Reading the file back to confirm the shape is a cheap check against silent mistakes.</p>
<h2>How a real project does this</h2>
<p>I have been reading the code of a Superstore dashboard built by <a href="https://github.com/Ashakalavagunta/sales-analytics-dashboard">Asha Kalavagunta</a> (I studied it; I did not write it). Its <code>etl_pipeline.py</code> follows the same stages, in a single top-to-bottom script:</p>
<table>
<thead>
<tr>
<th>Stage</th>
<th>What the project does</th>
</tr>
</thead>
<tbody><tr>
<td>Extract</td>
<td>Reads the raw Superstore CSV with <code>encoding='latin1'</code></td>
</tr>
<tr>
<td>Transform (clean)</td>
<td>Parses dates, converts Sales, Profit, Discount and Quantity to numbers, drops nulls and duplicates</td>
</tr>
<tr>
<td>Transform (features)</td>
<td>Adds Year, Month, Quarter, YearQuarter, Days_to_Ship, Profit_Margin and Revenue_Band</td>
</tr>
<tr>
<td>Validate</td>
<td>Assertions on positive sales, ship days, duplicates and null dates</td>
</tr>
<tr>
<td>Load</td>
<td>Writes <code>cleaned_data.csv</code> for the Streamlit app</td>
</tr>
</tbody></table>
<p>The README describes 9,994 rows and 21 raw columns becoming 30 cleaned columns. The app then reads the cleaned file, so charts and filters never touch raw data. That separation is the real lesson. Cleaning happens once, in one place.</p>
<p>Reading it, one thing I would try is making the validation checks able to fail on real problems, for example checking that cleaning actually removed zero-sales rows. That is a suggestion from a learner, not a criticism.</p>
<h2>Common ETL mistakes</h2>
<ul>
<li><strong>Cleaning after feature engineering.</strong> Build features on clean data only.</li>
<li><strong>Letting Pandas guess date formats.</strong> Pass <code>format=</code>.</li>
<li><strong>Overwriting the raw file.</strong> Keep the raw CSV untouched and write to a new file. You can always rerun.</li>
<li><strong>No validation.</strong> A pipeline that never complains is not necessarily correct.</li>
<li><strong>Hardcoding file names deep in the script.</strong> Put them at the top, as <code>RAW</code> and <code>OUT</code> are above.</li>
</ul>
<h2>Wrapping up</h2>
<p>ETL is just a habit: read, clean, add features, check, save. Pandas gives you everything needed for a small version of it, and the same structure scales to bigger projects.</p>
<p>Want to see the full version? Browse the original project by Asha Kalavagunta on <a href="https://github.com/Ashakalavagunta/sales-analytics-dashboard">GitHub</a>, and find more of my learning work at <a href="https://github.com/Pranaav717">github.com/Pranaav717</a>. The next post covers putting a project like this on GitHub: <a href="https://pranaavkarthik.hashnode.dev/github-for-beginners-local-project-to-repository">Git and GitHub for College Students: From Local Project to GitHub Repository</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Pandas for Beginners: Cleaning and Analysing Real-World Data]]></title><description><![CDATA[Real data is messy. Blank cells, repeated rows, column names with spaces in them. If you open a CSV in Excel you can fix these by hand, but that stops…]]></description><link>https://pranaavkarthik.hashnode.dev/pandas-for-beginners-cleaning-analysing-data</link><guid isPermaLink="true">https://pranaavkarthik.hashnode.dev/pandas-for-beginners-cleaning-analysing-data</guid><category><![CDATA[Python]]></category><category><![CDATA[pandas]]></category><category><![CDATA[Data Science]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Beginner Developers]]></category><dc:creator><![CDATA[Pranaav Karthik]]></dc:creator><pubDate>Wed, 11 Nov 2026 04:58:08 GMT</pubDate><content:encoded><![CDATA[<p>Real data is messy. Blank cells, repeated rows, column names with spaces in them. If you open a CSV in Excel you can fix these by hand, but that stops working once you have thousands of rows.</p>
<p>Pandas is the Python library that handles this. In this post we will clean a small sales file and then answer a few questions about it. Every snippet below was run with Pandas 2.3.3, and the outputs are copied from the terminal.</p>
<p>This is part of my series on building real-world projects with Python. If you want to see where Pandas fits in a full project, read <a href="https://pranaavkarthik.hashnode.dev/python-sales-analytics-dashboard-streamlit-pandas">What I Learned Studying a Python Sales Analytics Dashboard (Streamlit + Pandas)</a> first. Not required, though.</p>
<h2>What is Pandas?</h2>
<p>Pandas is a Python library for working with tabular data, the kind you see in a spreadsheet. You install it with:</p>
<pre><code class="language-bash">pip install pandas
</code></pre>
<p>Then import it with the usual short name:</p>
<pre><code class="language-python">import pandas as pd
</code></pre>
<p>Everything in Pandas revolves around two objects.</p>
<h3>Series</h3>
<p>A Series is one column of data. It is a list of values with an index (a label for each row).</p>
<pre><code class="language-python">s = pd.Series([260.5, 640.0, 18.2], name="sales")
print(s)
print(s.mean())
</code></pre>
<pre><code class="language-text">0    260.5
1    640.0
2     18.2
Name: sales, dtype: float64
306.23333333333335
</code></pre>
<h3>DataFrame</h3>
<p>A DataFrame is a table. Each column is a Series, and they all share the same index. Almost everything you do in Pandas happens on a DataFrame, so this is the one to get comfortable with.</p>
<h2>The dataset</h2>
<p>I made a small file called <code>sales_raw.csv</code> so the outputs fit on screen. It has 11 rows and a few deliberate problems: two missing sales values, one duplicated order, and column names with spaces.</p>
<pre><code class="language-text">order id,order date,region,category,product,sales,quantity,discount
1001,2024-01-05,West,Furniture,Bookcase,260.5,2,0.0
1002,2024-01-09,East,Technology,Phone,640.0,1,0.1
1003,2024-02-14,West,Office Supplies,Binder,18.2,3,0.2
1004,2024-02-20,Central,Technology,Keyboard,,2,0.0
1005,2024-03-02,East,Furniture,Chair,310.0,1,0.0
1005,2024-03-02,East,Furniture,Chair,310.0,1,0.0
1006,2024-03-18,South,Office Supplies,Paper,24.9,5,0.0
1007,2024-04-01,West,Technology,Monitor,420.0,2,0.15
1008,2024-04-12,Central,Furniture,Table,,1,0.3
1009,2024-05-07,South,Technology,Phone,610.0,1,0.0
1010,2024-05-21,East,Office Supplies,Binder,21.5,4,0.2
</code></pre>
<p>Can you spot the problems already? Pandas can find them for you.</p>
<h2>Reading the CSV</h2>
<p><code>read_csv</code> loads the file into a DataFrame.</p>
<pre><code class="language-python">import pandas as pd

pd.set_option("display.width", 200)
pd.set_option("display.max_columns", 20)

df = pd.read_csv("sales_raw.csv")
print(df.head())
print(df.shape)
df.info()
</code></pre>
<pre><code class="language-text">   order id  order date   region         category   product  sales  quantity  discount
0      1001  2024-01-05     West        Furniture  Bookcase  260.5         2       0.0
1      1002  2024-01-09     East       Technology     Phone  640.0         1       0.1
2      1003  2024-02-14     West  Office Supplies    Binder   18.2         3       0.2
3      1004  2024-02-20  Central       Technology  Keyboard    NaN         2       0.0
4      1005  2024-03-02     East        Furniture     Chair  310.0         1       0.0
(11, 8)
&lt;class 'pandas.core.frame.DataFrame'&gt;
RangeIndex: 11 entries, 0 to 10
Data columns (total 8 columns):
 #   Column      Non-Null Count  Dtype  
---  ------      --------------  -----  
 0   order id    11 non-null     int64  
 1   order date  11 non-null     object 
 2   region      11 non-null     object 
 3   category    11 non-null     object 
 4   product     11 non-null     object 
 5   sales       9 non-null      float64
 6   quantity    11 non-null     int64  
 7   discount    11 non-null     float64
dtypes: float64(2), int64(2), object(4)
memory usage: 836.0+ bytes
</code></pre>
<p>Three habits worth building here:</p>
<ul>
<li><code>head()</code> shows the first five rows. Always look at your data before touching it.</li>
<li><code>shape</code> gives (rows, columns).</li>
<li><code>info()</code> shows the type of each column and how many values are not null. Look at <code>sales</code>: 9 non-null out of 11. That is our first clue.</li>
</ul>
<p>Also notice that <code>order date</code> is <code>object</code>, which means plain text. Pandas has not understood that these are dates yet. We will fix that.</p>
<h2>Finding missing values</h2>
<p><code>isna()</code> marks each cell as missing or not. Chain <code>sum()</code> to count them per column.</p>
<pre><code class="language-python">print(df.isna().sum())
</code></pre>
<pre><code class="language-text">order id      0
order date    0
region        0
category      0
product       0
sales         2
quantity      0
discount      0
dtype: int64
</code></pre>
<p>Two missing in <code>sales</code>. To see the actual rows:</p>
<pre><code class="language-python">print(df[df["sales"].isna()])
</code></pre>
<pre><code class="language-text">   order id  order date   region    category   product  sales  quantity  discount
3      1004  2024-02-20  Central  Technology  Keyboard    NaN         2       0.0
8      1008  2024-04-12  Central   Furniture     Table    NaN         1       0.3
</code></pre>
<h2>Handling missing values: fillna or dropna</h2>
<p>You have two basic choices, and neither is always right.</p>
<p><strong>dropna</strong> removes the rows. Use it when the missing value makes the row useless.</p>
<pre><code class="language-python">print(len(df.dropna(subset=["sales"])))
</code></pre>
<pre><code class="language-text">9
</code></pre>
<p>The <code>subset</code> argument matters. Without it, Pandas drops a row if any column is missing.</p>
<p><strong>fillna</strong> replaces the gaps with something. Here I use the median, which is not pulled around by one huge order the way the mean is.</p>
<pre><code class="language-python">print("median:", df["sales"].median())
df["sales"] = df["sales"].fillna(df["sales"].median())
</code></pre>
<pre><code class="language-text">median: 310.0
</code></pre>
<p>A word of caution. Filling invents data. For a practice file it is fine. For something that feeds a report, ask whether dropping is more honest. A fair rule: if the missing field is the one you are about to sum, dropping is usually the safer choice.</p>
<h2>Removing duplicates</h2>
<p>Order 1005 appears twice. Check first, then remove.</p>
<pre><code class="language-python">print(df.duplicated().sum())
df = df.drop_duplicates()
print(df.shape)
</code></pre>
<pre><code class="language-text">1
(10, 8)
</code></pre>
<p><code>duplicated()</code> flags a row if an identical earlier row exists. <code>drop_duplicates()</code> keeps the first and removes the rest. Going from 11 rows to 10 is what we expected.</p>
<h2>Renaming columns and fixing types</h2>
<p>Column names with spaces are annoying, because you cannot use <code>df.order_id</code> style access. Rename them with a dictionary:</p>
<pre><code class="language-python">df = df.rename(columns={"order id": "order_id", "order date": "order_date"})
df["order_date"] = pd.to_datetime(df["order_date"])
print(df.dtypes)
</code></pre>
<pre><code class="language-text">order_id               int64
order_date    datetime64[ns]
region                object
category              object
product               object
sales                float64
quantity               int64
discount             float64
dtype: object
</code></pre>
<p><code>order_date</code> is now a real datetime. That unlocks things like <code>df["order_date"].dt.month</code>, which we will lean on in the next post.</p>
<h2>Filtering rows</h2>
<p>Filtering uses a boolean condition inside square brackets. Pandas builds a True/False value for every row and keeps the True ones.</p>
<p>Orders over 300:</p>
<pre><code class="language-python">print(df[df["sales"] &gt; 300])
</code></pre>
<pre><code class="language-text">   order_id order_date   region    category   product  sales  quantity  discount
1      1002 2024-01-09     East  Technology     Phone  640.0         1      0.10
3      1004 2024-02-20  Central  Technology  Keyboard  310.0         2      0.00
4      1005 2024-03-02     East   Furniture     Chair  310.0         1      0.00
7      1007 2024-04-01     West  Technology   Monitor  420.0         2      0.15
8      1008 2024-04-12  Central   Furniture     Table  310.0         1      0.30
9      1009 2024-05-07    South  Technology     Phone  610.0         1      0.00
</code></pre>
<p>See the two 310.0 rows for Keyboard and Table? Those are our filled values. A good reminder that fillna changes what your filters return.</p>
<p>Combine conditions with <code>&amp;</code> (and) or <code>|</code> (or). Put each condition in brackets:</p>
<pre><code class="language-python">print(df[(df["region"] == "West") &amp; (df["category"] == "Technology")])
</code></pre>
<pre><code class="language-text">   order_id order_date region    category  product  sales  quantity  discount
7      1007 2024-04-01   West  Technology  Monitor  420.0         2      0.15
</code></pre>
<p>Using Python's <code>and</code> here throws an error. It has to be <code>&amp;</code>. This one catches nearly everyone once.</p>
<h2>Grouping and aggregating</h2>
<p>This is where analysis starts. <code>groupby</code> splits the data into groups, and then you apply a calculation to each group.</p>
<p>Total sales per region:</p>
<pre><code class="language-python">print(df.groupby("region")["sales"].sum())
</code></pre>
<pre><code class="language-text">region
Central    620.0
East       971.5
South      634.9
West       698.7
Name: sales, dtype: float64
</code></pre>
<p>For several calculations at once, use <code>agg</code> with named outputs:</p>
<pre><code class="language-python">out = df.groupby("category").agg(
    total_sales=("sales", "sum"),
    avg_discount=("discount", "mean"),
    orders=("order_id", "count"),
).sort_values("total_sales", ascending=False)
print(out)
</code></pre>
<pre><code class="language-text">                 total_sales  avg_discount  orders
category                                          
Technology            1980.0      0.062500       4
Furniture              880.5      0.100000       3
Office Supplies         64.6      0.133333       3
</code></pre>
<p>The format is <code>new_name=("column", "function")</code>. Once you understand this pattern, a lot of dashboard-style questions become one short block of code. Technology brings in the most money here, and Office Supplies has the highest average discount while selling the least. That kind of comparison is exactly what a dashboard shows.</p>
<h2>The whole thing in one script</h2>
<p>Here is the cleaning part together, so you can copy it and run it.</p>
<pre><code class="language-python">import pandas as pd

df = pd.read_csv("sales_raw.csv")
df["sales"] = df["sales"].fillna(df["sales"].median())
df = df.drop_duplicates()
df = df.rename(columns={"order id": "order_id", "order date": "order_date"})
df["order_date"] = pd.to_datetime(df["order_date"])

print(df.groupby("category")["sales"].sum())
</code></pre>
<h2>A note on real projects</h2>
<p>Real files are bigger and messier than this one. I have been reading an open-source Superstore dashboard by <a href="https://github.com/Ashakalavagunta/sales-analytics-dashboard">Asha Kalavagunta</a>, and it uses exactly these tools on 9,994 rows: clean column names, parse dates, drop nulls and duplicates. The only difference is scale. The next post walks through how that cleaning is organised as a proper ETL pipeline.</p>
<h2>Common beginner mistakes</h2>
<ul>
<li><strong>Forgetting to assign.</strong> <code>df.drop_duplicates()</code> alone does not change <code>df</code>. Write <code>df = df.drop_duplicates()</code>.</li>
<li><strong>Using <code>and</code> instead of <code>&amp;</code></strong> in filters.</li>
<li><strong>Skipping <code>info()</code>.</strong> Dates stored as text will quietly break your month-wise charts.</li>
<li><strong>Filling without thinking.</strong> Decide on purpose, not by habit.</li>
</ul>
<h2>Wrapping up</h2>
<p>You now know the core loop: load, inspect, clean, filter, group. That covers a large share of everyday data work in Python.</p>
<p>Next up is ETL: <a href="https://pranaavkarthik.hashnode.dev/etl-using-python-pandas-practical-project">Understanding ETL in Python Through a Practical Project</a>. If you want more tutorials like this, follow the blog. My GitHub is at <a href="https://github.com/Pranaav717">github.com/Pranaav717</a>.</p>
]]></content:encoded></item><item><title><![CDATA[How to Build an Interactive Dashboard Using Python and Streamlit]]></title><description><![CDATA[Most Python tutorials end with a chart in a Jupyter notebook. That is fine for you, but nobody else can click around in it. A dashboard fixes that. You get…]]></description><link>https://pranaavkarthik.hashnode.dev/build-interactive-dashboard-python-streamlit</link><guid isPermaLink="true">https://pranaavkarthik.hashnode.dev/build-interactive-dashboard-python-streamlit</guid><category><![CDATA[Python]]></category><category><![CDATA[streamlit]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[data analytics]]></category><dc:creator><![CDATA[Pranaav Karthik]]></dc:creator><pubDate>Wed, 11 Nov 2026 04:55:29 GMT</pubDate><content:encoded><![CDATA[<p>Most Python tutorials end with a chart in a Jupyter notebook. That is fine for you, but nobody else can click around in it. A dashboard fixes that. You get filters, headline numbers and charts on one page, and anyone with the link can use it.</p>
<p>Streamlit lets you build that page with plain Python. No HTML, no JavaScript. In this tutorial you will build a small sales dashboard from scratch: a CSV file, a few KPI cards, sidebar filters and three Plotly charts. It is about 60 lines of code, and every line is shown below.</p>
<h2>What Streamlit actually does</h2>
<p>Streamlit is an open-source Python library that turns a script into a web app. You write top-to-bottom Python, call functions like <code>st.title()</code> or <code>st.metric()</code>, and each call puts something on the page.</p>
<p>One thing to understand early: <strong>Streamlit reruns your whole script every time someone touches a widget.</strong> Change a filter, and the script runs again from line 1. That sounds wasteful, but it keeps the mental model simple. It also explains why we cache the data loading step later.</p>
<p>If you want to see what a bigger finished dashboard looks like, there is an open-source Superstore dashboard by Asha Kalavagunta (<a href="https://github.com/Ashakalavagunta/sales-analytics-dashboard">GitHub repo</a>) with KPI cards, 14 charts and six filters. I went through its code while learning, and the small app here uses the same building blocks at a much smaller scale. <a href="https://pranaavkarthik.hashnode.dev/python-sales-analytics-dashboard-streamlit-pandas">What I Learned Studying a Python Sales Analytics Dashboard (Streamlit + Pandas)</a></p>
<h2>What you need</h2>
<ul>
<li>Python 3.10 or newer</li>
<li>A terminal and a code editor (VS Code is fine)</li>
<li>Streamlit 1.32 or newer, Pandas 2.x and Plotly</li>
</ul>
<p>The code in this post was run with Streamlit 1.67, Pandas 2.3 and Plotly 7. It uses nothing that is new in those versions, apart from one note about chart width that I mention in the charts section.</p>
<h2>Step 1: Set up the project</h2>
<p>Make a folder and a virtual environment. A virtual environment keeps this project's packages separate from everything else on your machine.</p>
<pre><code class="language-bash">mkdir sales-dashboard
cd sales-dashboard
python -m venv venv
</code></pre>
<p>Activate it. On Windows:</p>
<pre><code class="language-bash">venv\Scripts\activate
</code></pre>
<p>On macOS or Linux:</p>
<pre><code class="language-bash">source venv/bin/activate
</code></pre>
<p>Now install the three libraries:</p>
<pre><code class="language-bash">pip install streamlit pandas plotly
</code></pre>
<p>Check that Streamlit works:</p>
<pre><code class="language-bash">streamlit hello
</code></pre>
<p>A demo app opens in your browser. If it does, your setup is fine. Press <code>Ctrl+C</code> in the terminal to stop it.</p>
<h2>Step 2: Create the sample data</h2>
<p>You need a CSV with five columns: <code>Order Date</code>, <code>Region</code>, <code>Category</code>, <code>Sales</code>, <code>Profit</code>. Instead of pasting 60 rows into this post, here is a short script that generates the file. Save it as <code>make_data.py</code> and run <code>python make_data.py</code> once.</p>
<pre><code class="language-python">import csv, random
random.seed(7)
regions=["East","West","Central","South"]
cats={"Furniture":(120,500,-0.05,0.18),"Technology":(200,900,0.05,0.30),"Office Supplies":(20,200,0.02,0.25)}
import datetime
rows=[]
d=datetime.date(2023,1,1)
for i in range(60):
    od=datetime.date(2023,1,1)+datetime.timedelta(days=i*12+random.randint(0,6))
    c=random.choice(list(cats)); lo,hi,pl,ph=cats[c]
    s=round(random.uniform(lo,hi),2)
    rows.append([od.isoformat(),random.choice(regions),c,s,round(s*random.uniform(pl,ph),2)])
with open("sales.csv","w",newline="") as f:
    w=csv.writer(f); w.writerow(["Order Date","Region","Category","Sales","Profit"]); w.writerows(rows)
</code></pre>
<p>You now have <code>sales.csv</code>. The first rows look like this (your numbers will match mine because of the fixed random seed):</p>
<pre><code class="language-text">Order Date,Region,Category,Sales,Profit
2023-01-03,East,Furniture,270.03,-9.0
2023-01-17,East,Furniture,258.96,41.23
</code></pre>
<p>The data is made up, so don't read anything into the numbers. If you have a real CSV with the same five columns, use that instead. The app works the same way.</p>
<h2>Step 3: Write the app</h2>
<p>Create <code>app.py</code> in the same folder. Here is the full file. The sections after it explain each part.</p>
<pre><code class="language-python">import pandas as pd
import plotly.express as px
import streamlit as st

st.set_page_config(page_title="Sales Dashboard", layout="wide")
st.title("Sales Dashboard")

@st.cache_data
def load_data(path: str) -&gt; pd.DataFrame:
    df = pd.read_csv(path, parse_dates=["Order Date"])
    df["Month"] = df["Order Date"].dt.to_period("M").dt.to_timestamp()
    return df

df = load_data("sales.csv")

# Sidebar filters
st.sidebar.header("Filters")
regions = st.sidebar.multiselect(
    "Region", options=sorted(df["Region"].unique()), default=sorted(df["Region"].unique())
)
categories = st.sidebar.multiselect(
    "Category", options=sorted(df["Category"].unique()), default=sorted(df["Category"].unique())
)

filtered = df[df["Region"].isin(regions) &amp; df["Category"].isin(categories)]

if filtered.empty:
    st.warning("No data for these filters. Pick at least one region and one category.")
    st.stop()

# KPI cards
total_sales = filtered["Sales"].sum()
total_profit = filtered["Profit"].sum()
margin = total_profit / total_sales * 100

c1, c2, c3 = st.columns(3)
c1.metric("Total Sales", f"${total_sales:,.0f}")
c2.metric("Total Profit", f"${total_profit:,.0f}")
c3.metric("Profit Margin", f"{margin:.1f}%")

# Charts
monthly = filtered.groupby("Month", as_index=False)[["Sales", "Profit"]].sum()
fig_line = px.line(monthly, x="Month", y=["Sales", "Profit"], markers=True,
                   title="Monthly Sales and Profit")
st.plotly_chart(fig_line)

left, right = st.columns(2)
by_region = filtered.groupby("Region", as_index=False)["Sales"].sum()
left.plotly_chart(px.bar(by_region, x="Region", y="Sales", title="Sales by Region"))
by_cat = filtered.groupby("Category", as_index=False)["Profit"].sum()
right.plotly_chart(px.pie(by_cat, names="Category", values="Profit", hole=0.4,
                          title="Profit by Category"))

with st.expander("See the data"):
    st.dataframe(filtered)
</code></pre>
<p>Run it:</p>
<pre><code class="language-bash">streamlit run app.py
</code></pre>
<p>Your browser opens at <code>http://localhost:8501</code>.</p>
<h2>How the code works</h2>
<h3>Loading data with caching</h3>
<pre><code class="language-python">@st.cache_data
def load_data(path: str) -&gt; pd.DataFrame:
    df = pd.read_csv(path, parse_dates=["Order Date"])
    df["Month"] = df["Order Date"].dt.to_period("M").dt.to_timestamp()
    return df
</code></pre>
<p>Remember that Streamlit reruns the script on every interaction. Without caching, it would re-read the CSV each time you click a filter. <code>@st.cache_data</code> stores the returned DataFrame, so the function body only runs the first time (or when the file path argument changes).</p>
<p><code>parse_dates=["Order Date"]</code> tells Pandas to read that column as real dates, not text. That matters because the next line, <code>.dt.to_period("M").dt.to_timestamp()</code>, rounds each date down to the first day of its month. We use that <code>Month</code> column to group sales by month later.</p>
<p>One caveat: if you edit <code>sales.csv</code> while the app is running, the cache won't notice, because the function argument (the path) hasn't changed. Press <code>C</code> in the app and choose "Clear cache", or restart it.</p>
<h3>Sidebar filters</h3>
<pre><code class="language-python">regions = st.sidebar.multiselect(
    "Region", options=sorted(df["Region"].unique()), default=sorted(df["Region"].unique())
)
</code></pre>
<p><code>st.sidebar.multiselect</code> draws a dropdown in the left panel where people can pick several values. We fill the options from the data itself, so if a new region appears in the CSV, the filter picks it up without code changes. Setting <code>default</code> to all options means the dashboard starts unfiltered.</p>
<p>Then we filter the DataFrame:</p>
<pre><code class="language-python">filtered = df[df["Region"].isin(regions) &amp; df["Category"].isin(categories)]
</code></pre>
<p><code>.isin()</code> keeps rows whose value is in the selected list. The <code>&amp;</code> joins two conditions, and each one needs to stay in its own comparison. Everything below this line uses <code>filtered</code>, not <code>df</code>, so every number and chart reacts to the filters.</p>
<h3>Handling the empty case</h3>
<pre><code class="language-python">if filtered.empty:
    st.warning("No data for these filters. Pick at least one region and one category.")
    st.stop()
</code></pre>
<p>If someone clears every option, <code>filtered</code> has zero rows, and the profit margin line would divide by zero. <code>st.stop()</code> ends the script run right there, so nothing below it executes. Try it: remove all regions in the sidebar and you get the warning instead of a crash.</p>
<h3>KPI cards with st.metric</h3>
<pre><code class="language-python">c1, c2, c3 = st.columns(3)
c1.metric("Total Sales", f"${total_sales:,.0f}")
</code></pre>
<p><code>st.columns(3)</code> splits the page into three side-by-side containers. <code>st.metric(label, value)</code> shows a big number with a small label above it. The f-string format <code>:,.0f</code> adds thousand separators and drops the decimals. <code>st.metric</code> also accepts a <code>delta</code> argument if you want to show a change, such as vs last month, but we skip it here to keep the app simple.</p>
<h3>Charts with Plotly Express</h3>
<pre><code class="language-python">monthly = filtered.groupby("Month", as_index=False)[["Sales", "Profit"]].sum()
fig_line = px.line(monthly, x="Month", y=["Sales", "Profit"], markers=True,
                   title="Monthly Sales and Profit")
st.plotly_chart(fig_line)
</code></pre>
<p>The pattern is always the same: summarise with <code>groupby</code>, build a figure with <code>px.something</code>, then hand it to <code>st.plotly_chart</code>. Passing a list to <code>y</code> draws one line per column. <code>as_index=False</code> keeps <code>Month</code> as a normal column, which is what Plotly Express expects.</p>
<p>The bar chart and donut chart follow the same pattern. A donut is just <code>px.pie</code> with <code>hole=0.4</code>.</p>
<p>A note on chart width. In Streamlit 1.42 and later, Plotly charts stretch to the container by default, which is what this code relies on. If you are on 1.32 to 1.41, add <code>use_container_width=True</code> inside each <code>st.plotly_chart()</code> call or the charts will keep Plotly's default width. In the newest releases <code>use_container_width</code> is deprecated in favour of <code>width="stretch"</code>, so pick based on your version. The simplest fix is to upgrade Streamlit.</p>
<h2>Common beginner mistakes</h2>
<p><strong>Forgetting the script reruns.</strong> If you put <code>counter = 0</code> at the top and expect it to grow with clicks, it won't. Every rerun resets it. For values that must survive reruns, look at <code>st.session_state</code>.</p>
<p><strong>Running <code>python app.py</code>.</strong> This does not start the web server. You'll get a few warnings and no dashboard. Always use <code>streamlit run app.py</code>.</p>
<p><strong>Wrong working directory.</strong> <code>pd.read_csv("sales.csv")</code> looks in the folder you launched the command from. If you see <code>FileNotFoundError</code>, <code>cd</code> into the project folder first. <a href="https://pranaavkarthik.hashnode.dev/deploy-streamlit-app-community-cloud">How to Deploy a Python Streamlit Project Online (Free, Step by Step)</a> covers a more robust way to build paths.</p>
<p><strong>Filtering the original DataFrame.</strong> If you chart <code>df</code> instead of <code>filtered</code>, the widgets will appear to do nothing. Check which variable each chart uses.</p>
<p><strong>Column name typos.</strong> <code>"Order date"</code> and <code>"Order Date"</code> are different to Pandas. A <code>KeyError</code> mentioning a column name almost always means a spelling or case mismatch.</p>
<p><strong>Caching something that should change.</strong> <code>@st.cache_data</code> is for loading and heavy calculations. Don't wrap your filter logic in it unless you pass the filter values in as arguments.</p>
<h2>Where to go next</h2>
<p>Once this runs, try one change at a time:</p>
<ol>
<li>Add a date range filter with <code>st.sidebar.date_input</code>.</li>
<li>Add a fourth KPI, like average order value.</li>
<li>Swap the donut for a <code>px.scatter</code> of Sales vs Profit and see which orders lose money.</li>
<li>Load your own CSV with <code>st.file_uploader</code>.</li>
</ol>
<p>When you are ready to put it online, push the folder to GitHub and follow <a href="https://pranaavkarthik.hashnode.dev/deploy-streamlit-app-community-cloud">How to Deploy a Python Streamlit Project Online (Free, Step by Step)</a>. For cleaning messy data before it reaches a dashboard, see <a href="https://pranaavkarthik.hashnode.dev/pandas-for-beginners-cleaning-analysing-data">Pandas for Beginners: Cleaning and Analysing Real-World Data</a>.</p>
<h2>Wrap up</h2>
<p>You now have a working dashboard: cached data loading, multiselect filters, KPI cards and three interactive charts, all in one short Python file. The Streamlit docs at docs.streamlit.io are worth bookmarking, since every widget there has a runnable example.</p>
<p>If this was useful, follow the blog for the next posts in this series, and have a look at <a href="https://github.com/Pranaav717">my GitHub profile</a>. To see a much bigger dashboard built with the same tools, read through <a href="https://github.com/Ashakalavagunta/sales-analytics-dashboard">Asha Kalavagunta's Sales Analytics Dashboard</a>.</p>
]]></content:encoded></item><item><title><![CDATA[What I Learned Studying a Python Sales Analytics Dashboard (Streamlit + Pandas)]]></title><description><![CDATA[A student's walkthrough of an open-source Python sales analytics dashboard: ETL, derived columns, KPIs, charts and filters, with credit to its author.]]></description><link>https://pranaavkarthik.hashnode.dev/python-sales-analytics-dashboard-streamlit-pandas</link><guid isPermaLink="true">https://pranaavkarthik.hashnode.dev/python-sales-analytics-dashboard-streamlit-pandas</guid><category><![CDATA[Python]]></category><category><![CDATA[streamlit]]></category><category><![CDATA[data analytics]]></category><category><![CDATA[pandas]]></category><category><![CDATA[projects]]></category><dc:creator><![CDATA[Pranaav Karthik]]></dc:creator><pubDate>Wed, 11 Nov 2026 04:53:28 GMT</pubDate><content:encoded><![CDATA[<p>A quick note before anything else. I did not build this dashboard. It was created by Asha Kalavagunta, and her original repo is here: <a href="https://github.com/Ashakalavagunta/sales-analytics-dashboard">github.com/Ashakalavagunta/sales-analytics-dashboard</a>. I have a copy on my GitHub, and I spent time reading through the code to understand how a complete data project fits together.</p>
<p>This post is what I took away from that. If you are a student who knows some Python and wants to see what a real Pandas and Streamlit project looks like end to end, this is a good one to study.</p>
<h2>What the project is</h2>
<p>It is a sales dashboard built on the Kaggle Sample Superstore dataset. The data has 9,994 rows and 21 raw columns, covering January 2014 to December 2017 across four US regions. After cleaning, the table has 30 columns.</p>
<p>The stack is simple:</p>
<ul>
<li>Python 3.10+</li>
<li>Pandas and NumPy for the data work</li>
<li>Plotly Express and Graph Objects for charts</li>
<li>Streamlit for the web interface</li>
<li>Streamlit Community Cloud for hosting</li>
</ul>
<p>The repo is small. There are four files that matter: <code>app.py</code>, <code>etl_pipeline.py</code>, <code>cleaned_data.csv</code> and <code>requirements.txt</code>.</p>
<h2>The structure: two scripts, one clean file</h2>
<p>The first thing I liked is the separation. The cleaning logic lives in <code>etl_pipeline.py</code>. It reads the raw CSV and writes <code>cleaned_data.csv</code>. Then <code>app.py</code> reads that cleaned file and draws the dashboard.</p>
<p>So the flow is:</p>
<ol>
<li>Raw Superstore CSV</li>
<li><code>etl_pipeline.py</code> cleans it and adds new columns</li>
<li><code>cleaned_data.csv</code> is saved</li>
<li><code>app.py</code> loads it, applies filters, shows KPIs and charts</li>
</ol>
<p>Notice that <code>app.py</code> is not fragile about this. Its <code>load_data()</code> function tries <code>cleaned_data.csv</code> first, then falls back to the raw <code>Sample - Superstore.csv</code>. It is wrapped in <code>@st.cache_data(ttl=300)</code>, so Streamlit does not re-read the file on every click. That one decorator is worth remembering. Streamlit reruns the whole script on every interaction, and caching is how you keep that fast.</p>
<h2>The ETL step, in plain words</h2>
<p>ETL means extract, transform, load. The script literally labels its sections that way. Here is what happens.</p>
<p><strong>Extract.</strong> The CSV is loaded with <code>latin1</code> encoding:</p>
<pre><code class="language-python">df = pd.read_csv(RAW_FILE, encoding='latin1')
</code></pre>
<p>The Superstore file has characters that break the default UTF-8 reader, so this encoding argument matters. Good tip if you ever hit a decode error on a downloaded CSV.</p>
<p><strong>Clean.</strong> Column names are normalised so spaces and hyphens become underscores. That makes <code>Order Date</code> into <code>Order_Date</code>, which you can access cleanly in code:</p>
<pre><code class="language-python">df.columns = [c.strip().replace(' ', '_').replace('-', '_') for c in df.columns]
</code></pre>
<p>Dates are parsed and the numeric columns (Sales, Profit, Discount, Quantity) are coerced with <code>errors='coerce'</code>. That setting turns anything unparseable into a null instead of crashing. After that, rows missing Sales, Profit or Order_Date are dropped, and duplicates are removed.</p>
<p><strong>Transform.</strong> This is where new columns get created. More on that below.</p>
<p><strong>Validate.</strong> I think this is the most underrated part. The script ends with a handful of <code>assert</code> checks: sales must be positive, ship days cannot be negative, no duplicates remain, no null order dates. If any of these fail, the script stops. It is a cheap way to trust your own output.</p>
<h2>Derived columns (feature engineering)</h2>
<p>Raw data rarely has exactly what you want to chart. The ETL script builds these from the existing columns:</p>
<ul>
<li><code>Year</code>, <code>Month</code>, <code>Month_Name</code>, <code>Quarter</code> and <code>YearQuarter</code>, all from Order_Date</li>
<li><code>Days_to_Ship</code>, which is Ship_Date minus Order_Date</li>
<li><code>Profit_Margin</code>, which is profit divided by sales, times 100</li>
<li><code>Revenue_Band</code>, which buckets each order as Low, Medium, High or Premium</li>
</ul>
<p>Two lines show the style well:</p>
<pre><code class="language-python">df['Days_to_Ship']  = (df['Ship_Date'] - df['Order_Date']).dt.days.clip(lower=0)
df['Profit_Margin'] = (df['Profit'] / df['Sales'].replace(0, np.nan) * 100).round(2)
</code></pre>
<p>What I picked up here: the <code>.clip(lower=0)</code> stops negative ship days, and <code>.replace(0, np.nan)</code> avoids dividing by zero. Small defensive details like these are what separate a script that works once from one that keeps working.</p>
<p>The revenue bands use <code>pd.cut</code> with bins at 0, 50, 200 and 500, so orders fall into Low (under $50), Medium, High or Premium (over $500).</p>
<h2>KPI cards</h2>
<p>The top of the dashboard shows five numbers: Total Revenue, Net Profit, Total Orders, Unique Customers and Average Discount. They are computed straight from the filtered DataFrame. Total orders, for example, uses <code>nunique()</code> on Order_ID, because one order can have several rows.</p>
<p>That is a good Pandas lesson by itself. Counting rows and counting orders are not the same thing.</p>
<h2>The charts</h2>
<p>The README counts 14 charts. Reading through <code>app.py</code>, they cover:</p>
<ul>
<li>Monthly revenue and profit as a line chart</li>
<li>Revenue by region as a bar chart</li>
<li>Category split as a donut</li>
<li>Segment bar chart</li>
<li>Top sub-categories</li>
<li>Profit margin by sub-category</li>
<li>Ship mode as a donut</li>
<li>Discount vs profit as a scatter plot</li>
<li>Yearly revenue and profit as grouped bars</li>
<li>A US state choropleth map</li>
<li>Days to ship by mode</li>
<li>A month by year heatmap</li>
<li>A top 10 products table</li>
</ul>
<p>Every one is drawn with Plotly and shown with <code>st.plotly_chart(fig, use_container_width=True)</code>. Once you see one chart done, the others are variations. I found that reassuring. The pattern is: group the data with Pandas, pass it to Plotly, hand the figure to Streamlit.</p>
<h2>Filters</h2>
<p>The sidebar has six filters: year, quarter, region, segment, category and ship mode. There is also an order date range. All are multiselects, and the data is cut down with a single boolean mask:</p>
<pre><code class="language-python">mask = (
    df['Year'].isin(sel_years) &amp;
    df['Quarter'].isin(sel_quarters) &amp;
    df['Region'].isin(sel_regions) &amp;
    df['Segment'].isin(sel_segments) &amp;
    df['Category'].isin(sel_categories) &amp;
    df['Ship_Mode'].isin(sel_ship)
)
</code></pre>
<p>Then <code>fdf = df[mask].copy()</code> becomes the DataFrame every KPI and chart reads from. So changing one filter updates the entire page. This is the core idea of the whole app, and it is just <code>isin()</code> joined with <code>&amp;</code>. There is also a button to download the filtered data as CSV.</p>
<h2>What I would try differently</h2>
<p>These are my own thoughts as a learner, not criticism of Asha's work.</p>
<ul>
<li>Some of the insight card text in the app is typed in as fixed text rather than calculated from the data. I would try generating those lines from the filtered numbers.</li>
<li>I would experiment with splitting <code>app.py</code> into smaller files, since one script holds all the layout and charts.</li>
<li>The ETL logic appears twice in a similar form, once in <code>etl_pipeline.py</code> and once in <code>load_data()</code>. I would try to share one function.</li>
</ul>
<p>I have not built any of these yet. They are on my list for when I start my own project.</p>
<h2>Why this was worth studying</h2>
<p>Tutorials show you one concept at a time. A finished project shows you how the pieces connect: where cleaning ends, where charts begin, how filters touch everything. Reading someone else's code, with credit, is a fair way to learn.</p>
<p>If you want to explore it, go to <a href="https://github.com/Ashakalavagunta/sales-analytics-dashboard">Asha's original repository</a> and run the ETL script yourself. If you want to follow what I study and build next, my profile is <a href="https://github.com/Pranaav717">github.com/Pranaav717</a>.</p>
<p>For a gentler start on the Pandas side, see <a href="https://pranaavkarthik.hashnode.dev/pandas-for-beginners-cleaning-analysing-data">Pandas for Beginners: Cleaning and Analysing Real-World Data</a>. For the ETL idea in more depth, see <a href="https://pranaavkarthik.hashnode.dev/etl-using-python-pandas-practical-project">Understanding ETL in Python Through a Practical Project</a>.</p>
<p><em>All code snippets above are from Asha Kalavagunta's project, quoted briefly for learning.</em></p>
]]></content:encoded></item></channel></rss>