<?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: Mustapha Yusuf</title>
    <description>The latest articles on DEV Community by Mustapha Yusuf (@mustapha8484).</description>
    <link>https://dev.to/mustapha8484</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%2F1882955%2Fc2e67661-0d3c-419f-8bda-0c46df44cad2.png</url>
      <title>DEV Community: Mustapha Yusuf</title>
      <link>https://dev.to/mustapha8484</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mustapha8484"/>
    <language>en</language>
    <item>
      <title>I built a free tool that runs real regressions for students who can't afford Stata</title>
      <dc:creator>Mustapha Yusuf</dc:creator>
      <pubDate>Tue, 28 Jul 2026 17:34:21 +0000</pubDate>
      <link>https://dev.to/mustapha8484/i-built-a-free-tool-that-runs-real-regressions-for-students-who-cant-afford-stata-9k</link>
      <guid>https://dev.to/mustapha8484/i-built-a-free-tool-that-runs-real-regressions-for-students-who-cant-afford-stata-9k</guid>
      <description>&lt;p&gt;A few months ago I started building something small and it's turned into the most useful thing I've shipped in a while, so I wanted to write up how it actually works, since a lot of the interesting decisions were in what I &lt;em&gt;didn't&lt;/em&gt; build rather than what I did.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem, briefly
&lt;/h2&gt;

&lt;p&gt;When I was doing my economics degree, a good chunk of the class either couldn't afford Stata or EViews, or had it installed and running but genuinely didn't understand what the output meant. Durbin-Watson, Breusch-Pagan, VIF — these get taught for maybe one lecture and then you're expected to just know how to read them for the rest of your degree. And your supervisor has thirty other students, so they can't sit with you through every regression you run.&lt;/p&gt;

&lt;p&gt;So I built StatMate. You upload your data, tell it what you're testing (does X affect Y, a yes/no outcome, or something over time), it runs the actual statistical test, and it explains what came out in plain English instead of just spitting numbers at you.&lt;/p&gt;

&lt;p&gt;It's live at &lt;a href="https://getstatmate.com" rel="noopener noreferrer"&gt;getstatmate.com&lt;/a&gt; and the code's on &lt;a href="https://github.com/Mustaphayinka/statmate" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; if you want to poke at it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;Frontend's just HTML, JS, and Tailwind, sitting on Netlify. Backend's FastAPI on Render, and the actual statistics run through &lt;code&gt;statsmodels&lt;/code&gt;, not anything I wrote myself. That last part matters more than it sounds — I'll get to why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I actually spent time on: not letting anything guess a number
&lt;/h2&gt;

&lt;p&gt;Here's the decision that shaped basically everything else about the project. When I got to the part where StatMate explains the results in plain economic language, the obvious move is to throw the numbers at an LLM and ask it to write something nice.&lt;/p&gt;

&lt;p&gt;I didn't do that. Every explanation is built from a template engine with conditional logic — check the R-squared band, check which p-value threshold a coefficient falls under, check which diagnostics failed — and stitch together sentences from that. No language model touches the actual interpretation.&lt;/p&gt;

&lt;p&gt;Why. Because an LLM narrating freely can restate a coefficient slightly wrong and sound completely confident doing it. For a tool students are trusting with an actual grade, "confidently wrong" is worse than "boring but correct." The template can only ever say what its logic explicitly allows, built directly off numbers that already came out of a real regression. It literally cannot invent a result.&lt;/p&gt;

&lt;p&gt;A snippet of what that looks like, since code's more honest than a description:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_sig_label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not testable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;significant at the 1% level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;significant at the 5% level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;significant at the 10% level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not statistically significant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing clever. That's the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling three different kinds of questions
&lt;/h2&gt;

&lt;p&gt;Students don't all need the same test. So the question type a student picks routes to a genuinely different engine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Does X affect Y" → OLS, with the full diagnostic battery (Breusch-Pagan for heteroskedasticity, VIF for multicollinearity, Durbin-Watson for autocorrelation, Jarque-Bera for normality)&lt;/li&gt;
&lt;li&gt;"Does X affect the probability of Y" → logistic regression, reporting odds ratios and a pseudo R-squared instead of the OLS diagnostics, which don't apply here&lt;/li&gt;
&lt;li&gt;"Does X affect Y over time" → runs an Augmented Dickey-Fuller stationarity test on every variable &lt;em&gt;before&lt;/em&gt; the regression, because regressing one trending series on another gives you a spurious, meaningless R-squared that looks great and means nothing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is probably the thing I'm proudest of getting right. It's an easy mistake for a student to make and a genuinely common one in real undergrad time-series projects — running a regression on two things that are both just trending upward and mistaking the fit for a real relationship.&lt;/p&gt;

&lt;h2&gt;
  
  
  What breaks, and how I handle it breaking
&lt;/h2&gt;

&lt;p&gt;Early versions of this just let a raw exception surface if someone picked a text column as their Y variable, or picked the same column for both X and Y. That's a bad experience for a non-technical user, so the validation layer now catches these specifically and returns a plain sentence instead of a stack trace — "this column contains text, not numbers" rather than a &lt;code&gt;ValueError&lt;/code&gt; traceback.&lt;/p&gt;

&lt;p&gt;There's also a genuinely fun one: when X variables are perfectly correlated, &lt;code&gt;statsmodels&lt;/code&gt; throws a &lt;code&gt;LinAlgError&lt;/code&gt; because the underlying matrix becomes singular. Caught that one specifically too, since it's a real and common student mistake, not a bug in my code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Getting real students and lecturers to actually use it and tell me what's broken matters more right now than adding features. I've got a WhatsApp reply I'm hoping for from my department head, and I posted this on LinkedIn hoping a few coursemates mid-project would try it on real data.&lt;/p&gt;

&lt;p&gt;Longer term: panel data support, cointegration testing for the time series path, and probably a downloadable report format so the output can go straight into an actual project write-up instead of living only on screen.&lt;/p&gt;

&lt;p&gt;If you're a student, a lecturer, or just enjoy seeing what other people build, I'd genuinely like feedback: &lt;a href="https://getstatmate.com" rel="noopener noreferrer"&gt;getstatmate.com&lt;/a&gt;, repo's &lt;a href="https://github.com/Mustaphayinka/statmate" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
