<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: mbadr3227-sys</title>
    <description>The latest articles on DEV Community by mbadr3227-sys (@mbadr3227sys).</description>
    <link>https://dev.to/mbadr3227sys</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4065395%2Fb9c88a54-655a-4f03-a053-b2fc5a6f37de.png</url>
      <title>DEV Community: mbadr3227-sys</title>
      <link>https://dev.to/mbadr3227sys</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mbadr3227sys"/>
    <language>en</language>
    <item>
      <title>I Kept Rewriting the Same Five Lines of Pandas, So I Built a Terminal Tool Instead</title>
      <dc:creator>mbadr3227-sys</dc:creator>
      <pubDate>Thu, 06 Aug 2026 07:49:34 +0000</pubDate>
      <link>https://dev.to/mbadr3227sys/i-kept-rewriting-the-same-five-lines-of-pandas-so-i-built-a-terminal-tool-instead-44aa</link>
      <guid>https://dev.to/mbadr3227sys/i-kept-rewriting-the-same-five-lines-of-pandas-so-i-built-a-terminal-tool-instead-44aa</guid>
      <description>&lt;p&gt;Every time I opened a new dataset, I typed the same thing:&lt;/p&gt;

&lt;p&gt;df.shape&lt;br&gt;
df.head()&lt;br&gt;
df.isna().sum()&lt;br&gt;
df.describe()&lt;br&gt;
df.corr()&lt;/p&gt;

&lt;p&gt;Five lines, every single time, in a fresh notebook cell. It took maybe forty seconds. But it was forty seconds of typing something I already knew I wanted, before I could start on the part I actually cared about.&lt;/p&gt;

&lt;p&gt;So I built DataPeek — a terminal program that does all of it from a menu. You point it at a CSV, press a number, and read the answer.&lt;/p&gt;

&lt;p&gt;This was my portfolio project for Codecademy's CS 101: Introduction to Programming, and I want to write about the parts that surprised me, not just what the tool does.&lt;br&gt;
The shape of the thing&lt;/p&gt;

&lt;p&gt;The whole program is two files.&lt;/p&gt;

&lt;p&gt;main.py runs the interface: it prints a banner, loads a file, shows a menu, and loops until you quit. data_tools.py holds the analysis — one function per menu option, each taking a DataFrame and printing a report.&lt;/p&gt;

&lt;p&gt;The piece that ties them together is a dictionary:&lt;br&gt;
ACTIONS = {&lt;br&gt;
    "1": dt.show_overview,&lt;br&gt;
    "2": dt.show_head,&lt;br&gt;
    "3": dt.show_missing,&lt;br&gt;
    "4": dt.show_stats,&lt;br&gt;
    "5": dt.show_value_counts,&lt;br&gt;
    "6": dt.show_correlations,&lt;br&gt;
    "7": dt.filter_and_export,&lt;br&gt;
}&lt;br&gt;
My first version was a long if/elif chain. It worked, but every new feature meant editing the loop, and the loop kept growing. Storing the functions in a dictionary meant the loop stopped changing entirely:&lt;br&gt;
action = ACTIONS.get(choice)&lt;br&gt;
if action is None:&lt;br&gt;
    print("I don't know that option. Try a number from the menu, or 'q'.")&lt;br&gt;
    continue&lt;br&gt;
action(df)&lt;br&gt;
Adding a feature is now a function plus one line in the dictionary. That was the first moment the project taught me something I hadn't gone looking for: functions are values you can store, not just things you call.&lt;/p&gt;

&lt;p&gt;Users type whatever they want&lt;/p&gt;

&lt;p&gt;I had never written a program that asked for input before. Every prior exercise ran on data I controlled — a CSV I'd already inspected, a variable I'd set myself two lines earlier.&lt;/p&gt;

&lt;p&gt;The first time I ran DataPeek, I broke it within thirty seconds. I typed python main.py at the prompt — the program was already running, and it was asking me for a file path, but my hands typed the command anyway out of habit. Then I pressed Enter on an empty prompt to see what would happen.&lt;/p&gt;

&lt;p&gt;Both times, it held. It told me it needed a file path and asked again.&lt;/p&gt;

&lt;p&gt;That only worked because I'd already spent most of my later commits writing guards instead of features. The number prompt became this:&lt;br&gt;
def ask_int(prompt, default, low, high):&lt;br&gt;
    raw = input(prompt).strip()&lt;br&gt;
    if not raw:&lt;br&gt;
        return default&lt;br&gt;
    try:&lt;br&gt;
        value = int(raw)&lt;br&gt;
    except ValueError:&lt;br&gt;
        print(f"Not a number. Using {default}.")&lt;br&gt;
        return default&lt;br&gt;
    if not low &amp;lt;= value &amp;lt;= high:&lt;br&gt;
        print(f"Out of range. Using {default}.")&lt;br&gt;
        return default&lt;br&gt;
    return value&lt;br&gt;
Eleven lines to read one number. It felt like overkill when I wrote it, and correct the first time the program survived something I hadn't planned for.&lt;/p&gt;

&lt;p&gt;File loading got the same treatment — separate except blocks for a missing file, an empty file, a malformed CSV, and a permissions error, each with a message that says what to do next. pd.read_csv raises genuinely different exceptions for these, and catching them separately means the user gets "That doesn't look like a valid CSV" instead of a stack trace.&lt;/p&gt;

&lt;p&gt;The general lesson: input validation isn't a finishing touch. It's most of the work of making a program usable by anyone but you.&lt;/p&gt;

&lt;p&gt;Making a terminal readable&lt;/p&gt;

&lt;p&gt;Terminal output has no styling. No bold, no colour, no layout. All you have is spaces and newlines, and it turns out that's enough if you're deliberate.&lt;/p&gt;

&lt;p&gt;f-string alignment did most of the work:&lt;br&gt;
print(f"{col[:24]:&amp;lt;25}{count:&amp;gt;10,}{pct:&amp;gt;9.1f}%")&lt;br&gt;
Left-align the label, right-align the numbers, truncate anything too long, and the columns line up. Right-aligned numbers are easier to compare because the digits stack.&lt;/p&gt;

&lt;p&gt;For missing values I added a crude bar:&lt;/p&gt;

&lt;p&gt;bar = "#" * int(pct / 5)&lt;/p&gt;

&lt;p&gt;One # per five percent. It's about as simple as a visualisation gets, and it still beats reading percentages down a column — you see the worst offenders without comparing anything.&lt;/p&gt;

&lt;p&gt;The part that took longest wasn't the code&lt;/p&gt;

&lt;p&gt;I want to be honest about the time breakdown, because the tutorials never are.&lt;/p&gt;

&lt;p&gt;Writing the program took an afternoon. Getting it to run took considerably longer, and none of that time was spent on Python.&lt;/p&gt;

&lt;p&gt;PowerShell refused to activate the virtual environment — Windows blocks script execution by default, and the fix is a single Set-ExecutionPolicy command that I only found by reading the error message properly instead of panicking at the red text. Then pip install pandas died partway through with [Errno 28] No space left on device. My drive had 130 MB free out of 75 GB. Clearing temporary files bought me a gigabyte, which was just enough.&lt;/p&gt;

&lt;p&gt;Then Git wasn't installed. Then I ran git remote add pointing at a GitHub repository I hadn't created yet, and spent a minute confused by Repository not found before realising the repository genuinely did not exist.&lt;/p&gt;

&lt;p&gt;None of these are interesting problems. All of them are real ones, and they're the actual content of "build a project on your own machine" — the step that tutorials compress into a single line that reads set up your environment.&lt;/p&gt;

&lt;p&gt;Where the correlation code got interesting&lt;/p&gt;

&lt;p&gt;df.corr() gives a full matrix. Every pair appears twice, and the diagonal is all 1.0. Useful to look at, annoying to rank.&lt;/p&gt;

&lt;p&gt;To get a sorted list of distinct pairs, I walked only the upper triangle:&lt;/p&gt;

&lt;p&gt;for i, col_a in enumerate(corr.columns):&lt;br&gt;
    for col_b in corr.columns[i + 1:]:&lt;br&gt;
        value = corr.loc[col_a, col_b]&lt;br&gt;
        if pd.notna(value):&lt;br&gt;
            pairs.append((col_a, col_b, value))&lt;/p&gt;

&lt;p&gt;pairs.sort(key=lambda pair: abs(pair[2]), reverse=True)&lt;/p&gt;

&lt;p&gt;Slicing from i + 1 skips the diagonal and everything below it. Sorting on abs() means a correlation of -0.8 ranks above +0.3, which is what you want — strength matters more than direction when you're scanning for what to look at next.&lt;/p&gt;

&lt;p&gt;What I'd do next&lt;br&gt;
ASCII histograms, so numeric distributions are visible not just summarised&lt;br&gt;
Group-by summaries, since that's the next thing I always reach for&lt;br&gt;
Saving a full report to a text file&lt;br&gt;
What it actually taught me&lt;/p&gt;

&lt;p&gt;I expected to learn pandas. I already knew pandas. What I learned was that the distance between code that works and code someone else can use is mostly made of error handling, clear messages, and structure that lets you add things without breaking what's there — plus a surprising amount of getting a machine to cooperate before any of it runs at all.&lt;/p&gt;

&lt;p&gt;The code is on GitHub: github.com/mbadr3227-sys/datapeek&lt;/p&gt;

&lt;p&gt;If you also keep typing the same five lines, feel free to steal it.&lt;/p&gt;

&lt;p&gt;Content&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>datascience</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
