<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>logistic regression &#8211; paologironi blog</title>
	<atom:link href="https://www.gironi.it/blog/en/tag/logistic-regression/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.gironi.it/blog</link>
	<description>Scattered notes on (retro) computing, data analysis, statistics, SEO, and things that change</description>
	<lastBuildDate>Mon, 12 Oct 2026 08:32:12 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	
	<item>
		<title>Logistic Regression: What It Is, How to Read Odds Ratios, and an Example in R</title>
		<link>https://www.gironi.it/blog/en/logistic-regression-predicting-the-outcome-of-an-event/</link>
					<comments>https://www.gironi.it/blog/en/logistic-regression-predicting-the-outcome-of-an-event/#respond</comments>
		
		<dc:creator><![CDATA[paolo]]></dc:creator>
		<pubDate>Thu, 12 Jan 2023 12:07:22 +0000</pubDate>
				<category><![CDATA[statistics]]></category>
		<category><![CDATA[ai]]></category>
		<category><![CDATA[logistic regression]]></category>
		<guid isPermaLink="false">https://www.gironi.it/blog/?p=3280</guid>

					<description><![CDATA[Sooner or later, every traffic analysis arrives at the question that really matters: did this visit turn into a customer, yes or no? We don&#8217;t care how much a session converted, we care whether it did. And right behind it comes the next question: which sessions are more likely to convert? The ones arriving from &#8230; <a href="https://www.gironi.it/blog/en/logistic-regression-predicting-the-outcome-of-an-event/" class="more-link">Continue reading<span class="screen-reader-text"> "Logistic Regression: What It Is, How to Read Odds Ratios, and an Example in R"</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Sooner or later, every traffic analysis arrives at the question that really matters: did this visit turn into a customer, yes or no? We don&#8217;t care <em>how much</em> a session converted, we care <em>whether</em> it did. And right behind it comes the next question: which sessions are more likely to convert? The ones arriving from a search for the brand name? The ones browsing more pages? The ones on desktop?</p>



<p class="wp-block-paragraph">With <a href="https://www.gironi.it/blog/en/correlation-and-regression-analysis-linear-regression/">linear regression</a> we learned to predict a number. Here, though, the outcome is a switch, on or off, and a straight line is not the right tool: we&#8217;ll see it even ends up predicting negative probabilities. The right tool is <strong>logistic regression</strong>, one of the most widely used models of all, from marketing to medicine. Let&#8217;s build it together, starting from an example with conversions.</p>



<span id="more-3280"></span>



<p class="wp-block-paragraph"><strong>In short:</strong> <strong>logistic regression</strong> is the statistical model that estimates the <strong>probability of a binary outcome</strong> (converts or not, clicks or not) from one or more variables. Instead of a straight line it uses the <strong>logistic function</strong>, an S-shaped curve that always stays between 0 and 1. The coefficients are expressed in <em>log-odds</em>: exponentiated, they become <strong>odds ratios</strong>, the factor by which the odds of the event are multiplied when a variable increases by one unit. In R it is fitted with <code>glm(..., family = binomial)</code> and predicted probabilities come from <code>predict(..., type = "response")</code>. To classify, we then need a <strong>threshold</strong>, which with rare events has to be chosen with care: 0.5 is almost never the right one.</p>



<p class="wp-block-paragraph"><strong>What we&#8217;ll cover</strong>:</p>



<ul class="wp-block-list"><li><a href="#why-not-a-line">Why isn&#8217;t a straight line enough?</a></li><li><a href="#logistic-equation">From probability to odds to logit: the logistic equation</a></li><li><a href="#example-r">How do we fit a logistic regression in R?</a></li><li><a href="#odds-ratio">How do we read the coefficients? Odds ratios</a></li><li><a href="#threshold">From probabilities to decisions: where to put the threshold?</a></li><li><a href="#assumptions">The conditions for using it</a></li><li><a href="#faq">Frequently asked questions</a></li><li><a href="#try-it-yourself">Try it yourself</a></li><li><a href="#further-reading">Further reading</a></li></ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="why-not-a-line">Why isn&#8217;t a straight line enough?</h2>



<p class="wp-block-paragraph">Let&#8217;s take 5,000 organic sessions on an e-commerce site. For each one we know three things: how many pages it viewed, whether it came from a smartphone or a desktop, and whether the starting query contained the brand name. And we know whether the session ended with a purchase: 271 converted, 5.4%. The data are simulated, so we know the truth and can check whether the model recovers it.</p>



<p class="wp-block-paragraph">The most immediate idea is to treat conversion as a number, 1 if it happened and 0 if not, and run a least-squares line through it. It even has a name, the <em>linear probability model</em>, and in some cases it works acceptably. But let&#8217;s see what happens with our data.</p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://www.gironi.it/blog/wp-content/uploads/2026/10/regressione-logistica-retta-curva-en.png" alt="Observed conversion rate by number of pages viewed (the bubbles, whose area shows how many sessions there are) with the least-squares line and the logistic curve. The line always adds 1.9 points per page and stays below the data on the right; the curve rises slowly and then speeds up, following the bubbles."/><figcaption class="wp-element-caption">Observed conversion rate by number of pages viewed (the bubbles, whose area shows how many sessions there are) with the least-squares line and the logistic curve. The line always adds 1.9 points per page and stays below the data on the right; the curve rises slowly and then speeds up, following the bubbles.</figcaption></figure>



<p class="wp-block-paragraph">The line says every extra page is always worth 1.9 percentage points of conversion, whether we go from 1 to 2 pages or from 8 to 9. The data tell another story: sessions with few pages convert very little and almost regardless, then the probability speeds up. And when we also add device and brand, things get worse: <strong>the linear model assigns 654 sessions out of 5,000 a negative conversion probability</strong>, down to −2.2%. A probability below zero has no meaning at all. At the other end, it gives the best profile (12 pages, desktop, brand query) 28.8%, where logistic regression, as we&#8217;ll see, estimates 72.9%.</p>



<p class="wp-block-paragraph">The problem is one of shape. A probability lives between 0 and 1, and near the edges it has to &#8220;brake&#8221;: it can&#8217;t go above 100% or below zero. A straight line, instead, goes on forever. We need a curve with built-in brakes.</p>



<h2 class="wp-block-heading" id="logistic-equation">From probability to odds to logit: the logistic equation</h2>



<p class="wp-block-paragraph">Logistic regression solves the problem with a change of scale in two steps. The first step is the <strong>odds</strong>, a word anyone familiar with betting knows well: instead of the probability that an event happens, we look at the ratio between the probability that it happens and the probability that it doesn&#8217;t.</p>



<p class="wp-block-paragraph">\(\text{odds} = \frac{p}{1-p}\)</p>



<p class="wp-block-paragraph">With a probability of 20%, the odds are 0.2 / 0.8 = 0.25: &#8220;one to four&#8221;, one success for every four failures. At 50% the odds are 1, and at 80% they rise to 4. The odds have no ceiling at 1: they run from zero to infinity.</p>



<p class="wp-block-paragraph">The second step is to take their logarithm. The <strong>logarithm of the odds</strong>, called the <em>logit</em>, also removes the floor at zero and can take any value, from minus infinity to plus infinity. And it is precisely on this unconstrained scale that logistic regression places its linear equation, identical in form to that of <a href="https://www.gironi.it/blog/en/multiple-regression-analysis-explained-simply/">multiple regression</a>:</p>



<p class="wp-block-paragraph">\(\ln\left(\frac{p}{1-p}\right) = b_0 + b_1 x_1 + b_2 x_2 + \dots + b_k x_k\)</p>



<p class="wp-block-paragraph">where p is the probability of the event, the x&#8217;s are the variables (the <em>predictors</em>) and the b&#8217;s are the coefficients to estimate. To go back to the probability we just reverse the path, and we get the <strong>logistic function</strong>, also called the <em>sigmoid</em> because of its S shape:</p>



<p class="wp-block-paragraph">\(p = \frac{1}{1 + e^{-(b_0 + b_1 x_1 + \dots + b_k x_k)}}\)</p>



<p class="wp-block-paragraph">Whatever value the expression in brackets takes, the result always stays between 0 and 1. These are the built-in brakes: <strong>logistic regression is a straight line on the log-odds scale, which becomes an S-shaped curve on the probability scale</strong>.</p>



<p class="wp-block-paragraph">The coefficients are not found by least squares but by <em>maximum likelihood</em>: among all possible curves, we pick the one that makes the data we actually observed most probable. The calculation is iterative, and we gladly leave it to the software.</p>



<h2 class="wp-block-heading" id="example-r">How do we fit a logistic regression in R?</h2>



<p class="wp-block-paragraph">Let&#8217;s simulate the 5,000 sessions. In real life the data would come from Google Analytics or the CRM; here we build them ourselves, with known effects, so we can check the result:</p>



<pre class="wp-block-code"><code>set.seed(7)
n &lt;- 5000
sessions &lt;- data.frame(
  pages  = 1 + rpois(n, 2.5),    # pages viewed in the session
  mobile = rbinom(n, 1, 0.6),    # 1 = smartphone, 0 = desktop
  brand  = rbinom(n, 1, 0.25)    # 1 = arrived from a query with the brand name
)
true_logit &lt;- -4.4 + 0.35 * sessions$pages - 0.5 * sessions$mobile + 1.2 * sessions$brand
sessions$converts &lt;- rbinom(n, 1, plogis(true_logit))

mean(sessions$converts)
# [1] 0.0542</code></pre>



<p class="wp-block-paragraph">The <code>plogis()</code> function is the logistic function itself: it turns the true logit into a probability, and <code>rbinom()</code> tosses a loaded coin with that probability for each session. That&#8217;s why <code>family = binomial</code> appears in the model: each session is a Bernoulli trial, the building block of the <a href="https://www.gironi.it/blog/en/probability-distributions-discrete-distributions-and-the-binomial/">binomial distribution</a>.</p>



<p class="wp-block-paragraph">Let&#8217;s fit the model:</p>



<pre class="wp-block-code"><code>model &lt;- glm(converts ~ pages + mobile + brand, data = sessions, family = binomial)
summary(model)
# Coefficients:
#             Estimate Std. Error z value Pr(&gt;|z|)
# (Intercept) -4.37566    0.18872 -23.187  &lt; 2e-16 ***
# pages        0.34742    0.03584   9.693  &lt; 2e-16 ***
# mobile      -0.50057    0.12834  -3.900 9.61e-05 ***
# brand        1.19691    0.12868   9.302  &lt; 2e-16 ***
#
#     Null deviance: 2107.0  on 4999  degrees of freedom
# Residual deviance: 1923.8  on 4996  degrees of freedom
# AIC: 1931.8</code></pre>



<p class="wp-block-paragraph">The model recovers almost perfectly the values we used to build the data: 0.347 against 0.35 for pages, −0.501 against −0.5 for mobile, 1.197 against 1.2 for brand. The columns read as in linear regression: estimate, standard error, the z statistic (here instead of t) and the <a href="https://www.gironi.it/blog/en/p-value-misconceptions/">p-value</a>. The <em>deviance</em> is the logistic counterpart of the residual sum of squares: it drops from 2107 (the model with no predictors) to 1924, a sign that the three variables explain something.</p>



<p class="wp-block-paragraph">The sign of the coefficients already tells us a lot: more pages and brand queries push the conversion probability up, smartphones push it down. But by how much? Here logistic regression asks for one more step.</p>



<h2 class="wp-block-heading" id="odds-ratio">How do we read the coefficients? Odds ratios</h2>



<p class="wp-block-paragraph">The coefficients are on the log-odds scale, which nobody is used to thinking in. The trick is to exponentiate them: <strong>e raised to the coefficient is the odds ratio</strong>, the number by which the odds of conversion are multiplied when the variable increases by one unit, holding the others fixed. Let&#8217;s compute them together with their <a href="https://www.gironi.it/blog/en/confidence-intervals-what-they-are-how-to-calculate-them-and-what-they-do-not-mean/">confidence intervals</a>:</p>



<pre class="wp-block-code"><code>round(exp(cbind(OR = coef(model), confint(model))), 3)
#                OR 2.5 % 97.5 %
# (Intercept) 0.013 0.009  0.018
# pages       1.415 1.319  1.519
# mobile      0.606 0.471  0.780
# brand       3.310 2.572  4.261</code></pre>



<p class="wp-block-paragraph">Let&#8217;s translate row by row:</p>



<ul class="wp-block-list"><li><strong>pages</strong>: each extra page viewed multiplies the odds of conversion by 1.415, which means they grow by about 41.5% (with an interval between +32% and +52%);</li><li><strong>mobile</strong>: on a smartphone the odds are 60.6% of those on desktop, about 40% lower;</li><li><strong>brand</strong>: a session arriving from a query with the brand name has odds 3.3 times those of a non-brand session.</li></ul>



<p class="wp-block-paragraph">None of the intervals contains 1, the value that would mean &#8220;no effect&#8221;.</p>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1.5rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow">
<p><strong>Watch out: the odds ratio is not a ratio of probabilities.</strong> &#8220;Odds 3.3 times higher&#8221; does not mean &#8220;3.3 times more likely&#8221;. The two almost coincide only when the event is rare, like our 5% conversions. With frequent events they drift far apart: going from odds of 1 to odds of 3.3 means going from a 50% to a 77% probability, which is certainly not triple. Above all, <strong>the same odds ratio produces different jumps in percentage points</strong>, depending on where we are on the curve.</p>
</div></div>



<p class="wp-block-paragraph">This last point deserves a chart. Let&#8217;s compute the predicted probabilities for four session profiles, with <code>type = "response"</code>, which returns probabilities instead of log-odds:</p>



<pre class="wp-block-code"><code>profiles &lt;- data.frame(pages  = c(3, 3, 8, 8),
                       mobile = c(1, 0, 1, 0),
                       brand  = c(0, 1, 0, 1))
round(predict(model, newdata = profiles, type = "response"), 3)
#     1     2     3     4
# 0.021 0.106 0.109 0.401</code></pre>



<p class="wp-block-paragraph">A smartphone session, non-brand, with 3 pages viewed converts with a 2.1% probability. The same number of pages on desktop and from a brand query is worth 10.6%. With 8 pages, desktop and brand, we reach 40.1%.</p>



<p class="wp-block-paragraph">Let&#8217;s check the first value by hand, to see there&#8217;s no magic. The logit is −4.376 + 0.347 × 3 − 0.501 × 1 + 1.197 × 0 = −3.834. The probability is 1 / (1 + e^3.834) = 1 / (1 + 46.2) = <strong>0.021</strong>.</p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://www.gironi.it/blog/wp-content/uploads/2026/10/regressione-logistica-profili-en.png" alt="Conversion probability predicted by the model as a function of pages viewed, for four kinds of session: brand or non-brand query, desktop or mobile. The curves have the same shape but start from different levels, and the higher they are the further apart they get."/><figcaption class="wp-element-caption">Conversion probability predicted by the model as a function of pages viewed, for four kinds of session: brand or non-brand query, desktop or mobile. The curves have the same shape but start from different levels, and the higher they are the further apart they get.</figcaption></figure>



<p class="wp-block-paragraph">The four curves share the same odds ratio per page, 1.415. And yet <strong>in percentage points the effect of one extra page changes enormously</strong>: for a mobile non-brand session, going from 2 to 3 pages adds 0.6 points (from 1.5% to 2.1%); for a mobile session from a brand query, going from 8 to 9 adds 7.6 (from 28.9% to 36.5%). That&#8217;s the S-curve at work, and it&#8217;s why in reports it pays to show both the odds ratio and a few predicted probabilities for concrete profiles.</p>



<h2 class="wp-block-heading" id="threshold">From probabilities to decisions: where to put the threshold?</h2>



<p class="wp-block-paragraph">Often we want a decision from the model: which sessions to show an offer to, which users to put into a remarketing campaign. To turn a probability into a yes or a no we need a <strong>threshold</strong>, and the instinctive choice is 0.5: if the probability is above 50%, we predict a conversion.</p>



<p class="wp-block-paragraph">To judge the model honestly, though, we can&#8217;t evaluate it on the same data we fitted it on. Let&#8217;s split the sessions: we fit on 3,500 and measure the predictions on the other 1,500, which the model has never seen.</p>



<pre class="wp-block-code"><code>set.seed(11)
rows &lt;- sample(n, 3500)
train &lt;- sessions[rows, ]
test &lt;- sessions[-rows, ]
model_train &lt;- glm(converts ~ pages + mobile + brand, data = train, family = binomial)
p_test &lt;- predict(model_train, newdata = test, type = "response")

table(predicted = p_test &gt; 0.5, actual = test$converts)
#          actual
# predicted    0    1
#     FALSE 1410   89
#     TRUE     1    0
mean((p_test &gt; 0.5) == test$converts)
# [1] 0.94</code></pre>



<p class="wp-block-paragraph">94% accuracy: it looks like a great result. But let&#8217;s look at the table: of the 89 sessions that actually converted, the model identified <strong>none</strong>. It simply answered &#8220;no&#8221; almost every time. And with a 5.9% conversion rate, anyone who always answers &#8220;no&#8221;, with no model at all, gets 94.1% accuracy, better than us.</p>



<p class="wp-block-paragraph"><strong>With rare events, accuracy is a misleading metric.</strong> The model isn&#8217;t useless: it&#8217;s the threshold that&#8217;s wrong. In our data almost no session goes above a 50% probability, so a 0.5 threshold amounts to never flagging anything. Let&#8217;s lower it to 0.1:</p>



<pre class="wp-block-code"><code>table(predicted = p_test &gt; 0.1, actual = test$converts)
#          actual
# predicted    0    1
#     FALSE 1292   62
#     TRUE   119   27</code></pre>



<p class="wp-block-paragraph">Now the model flags 146 sessions and catches 27 that really convert: 30% of conversions (the <em>sensitivity</em>, or <em>recall</em>). Among the flagged sessions, 18.5% convert (the <em>precision</em>), more than three times the starting 5.9%. Accuracy drops to 88%, but the model has become useful.</p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://www.gironi.it/blog/wp-content/uploads/2026/10/regressione-logistica-soglia-en.png" alt="Conversion probabilities predicted by the model for the 1,500 test sessions, split between those that did not convert (top) and those that converted (bottom). Almost everything sits below 30%: the 0.5 threshold catches no conversions, the 0.1 threshold catches 27 at the price of 119 false alarms."/><figcaption class="wp-element-caption">Conversion probabilities predicted by the model for the 1,500 test sessions, split between those that did not convert (top) and those that converted (bottom). Almost everything sits below 30%: the 0.5 threshold catches no conversions, the 0.1 threshold catches 27 at the price of 119 false alarms.</figcaption></figure>



<p class="wp-block-paragraph">The chart shows the trade-off. Converting sessions sit further to the right on average, but the two distributions overlap a lot: every threshold is a swap between conversions caught and false alarms. The right threshold isn&#8217;t decided by statistics, it&#8217;s decided by the cost of the errors: if showing an offer to someone who won&#8217;t buy costs little, a low threshold pays off.</p>



<p class="wp-block-paragraph">To judge the model independently of the threshold, the <strong>AUC</strong> is often used, the area under the ROC curve: it is the probability that, taking at random one session that converts and one that doesn&#8217;t, the model gives the first a higher probability. It&#8217;s 0.5 for a model that guesses and 1 for a perfect one. Let&#8217;s compute it with ranks, the same idea behind the <a href="https://www.gironi.it/blog/en/non-parametric-tests-the-wilcoxon-test-for-non-normal-data/">Wilcoxon test</a>:</p>



<pre class="wp-block-code"><code>r &lt;- rank(p_test)
n1 &lt;- sum(test$converts); n0 &lt;- sum(1 - test$converts)
round((sum(r[test$converts == 1]) - n1 * (n1 + 1) / 2) / (n1 * n0), 3)
# [1] 0.749</code></pre>



<p class="wp-block-paragraph">An AUC of 0.75 is a fair result: with only three variables, the model orders three pairs out of four correctly.</p>



<h2 class="wp-block-heading" id="assumptions">The conditions for using it</h2>



<p class="wp-block-paragraph">Logistic regression asks for less than linear regression (no normality of residuals, no constant variance), but a few conditions remain:</p>



<ul class="wp-block-list"><li><strong>the outcome is binary</strong>: only two categories (for three or more, see the frequently asked questions below);</li><li><strong>the observations are independent</strong>: ten sessions from the same user are not ten independent pieces of information;</li><li><strong>the relationship is linear on the logit scale</strong>: if the effect of pages grows up to a point and then flattens out, it has to be modelled (for example with bands of pages viewed);</li><li><strong>the predictors are not too correlated with each other</strong>, the multicollinearity problem we&#8217;ll discuss in the next article;</li><li><strong>there are enough events</strong>: a widespread rule of thumb asks for at least ten or so events (in our case, conversions) per predictor. We have 271 for 3 variables, well above that.</li></ul>



<p class="wp-block-paragraph">Finally there is a less known condition, and it&#8217;s not unusual to read the exact opposite: that logistic regression is used &#8220;when the data are linearly separable&#8221;. <strong>The opposite is true: if a variable perfectly separates the yeses from the noes, logistic regression breaks down.</strong> Maximum likelihood pushes the coefficient towards infinity to turn the curve into a perfect step, and the standard error explodes. It happens more often than we&#8217;d think, even with famous datasets.</p>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1.5rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow">
<p><strong>The Titanic case: when a coefficient explodes.</strong> The Titanic passenger dataset is the most widely used logistic regression example in the world. Let&#8217;s model survival as a function of class, sex, age, family on board, fare and port of embarkation, and a strange row shows up in the <code>summary()</code>: the intercept is 17.9 with a standard error of 607, and the three ports have coefficients around −12, also with huge errors.<br><br>The reason is two passengers, Amelie Icard and Martha Stone, whose port of embarkation is empty in the file. R treats the empty value as a fourth category, and uses it as the reference. Both survived: for that &#8220;category&#8221; the survival rate is 100%, a perfect separation. Historical sources show that both boarded at Southampton: assigning them &#8220;S&#8221;, the intercept goes back to 5.64 with a standard error of 0.64, and the ports become readable.<br><br>Two lessons. First: a standard error in the hundreds is almost always a sign of separation, not a result. Second: the same <code>glm()</code> silently dropped 177 passengers out of 891, those with no age. Before looking at the coefficients it&#8217;s always worth checking with <code>nobs(model)</code> how many rows it actually worked on.</p>
</div></div>



<h2 class="wp-block-heading" id="faq">Frequently asked questions</h2>



<h3 class="wp-block-heading">What is the difference between linear and logistic regression?</h3>



<p class="wp-block-paragraph">Linear regression predicts a continuous numeric variable, such as traffic or revenue. Logistic regression predicts the probability of a binary outcome, such as converts or doesn&#8217;t convert. To do so it models the logarithm of the odds linearly and returns probabilities that always lie between 0 and 1.</p>



<h3 class="wp-block-heading">What is the odds ratio in logistic regression?</h3>



<p class="wp-block-paragraph">It is the exponential of a coefficient: it tells by how much the odds of the event are multiplied when the variable increases by one unit, holding the others fixed. An odds ratio of 1.4 means odds 40% higher, one of 0.6 odds 40% lower, one equal to 1 no effect.</p>



<h3 class="wp-block-heading">How do we get predicted probabilities from a logistic regression?</h3>



<p class="wp-block-paragraph">By applying the logistic function to the linear combination of the coefficients: p = 1 / (1 + e^−(b0 + b1x1 + … + bkxk)). In R, <code>predict(model, newdata, type = "response")</code> returns the probabilities directly; without <code>type = "response"</code> it returns log-odds.</p>



<h3 class="wp-block-heading">What if there are more than two categories?</h3>



<p class="wp-block-paragraph">If the categories have no order (for example desktop, mobile and tablet), we use multinomial logistic regression, in R with <code>multinom()</code> from the nnet package. If they have a natural order (low, medium, high), we use ordinal logistic regression, in R with <code>polr()</code> from the MASS package.</p>



<h3 class="wp-block-heading">What is the right threshold for classifying?</h3>



<p class="wp-block-paragraph">It depends on the cost of the two kinds of error and on how frequent the event is. With rare events, such as conversions, a 0.5 threshold often leads to never flagging anything. It&#8217;s worth looking at the prediction table for several thresholds, or at the AUC to judge the model independently of the threshold.</p>



<h3 class="wp-block-heading">How much data does a logistic regression need?</h3>



<p class="wp-block-paragraph">What matters most is the number of events, the cases in the less frequent category. A widespread rule of thumb asks for at least ten or so per predictor: with 50 conversions, better not to go beyond four or five variables.</p>



<script type="application/ld+json">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What is the difference between linear and logistic regression?", "acceptedAnswer": {"@type": "Answer", "text": "Linear regression predicts a continuous numeric variable, such as traffic or revenue. Logistic regression predicts the probability of a binary outcome, such as converts or doesn't convert. To do so it models the logarithm of the odds linearly and returns probabilities that always lie between 0 and 1."}}, {"@type": "Question", "name": "What is the odds ratio in logistic regression?", "acceptedAnswer": {"@type": "Answer", "text": "It is the exponential of a coefficient: it tells by how much the odds of the event are multiplied when the variable increases by one unit, holding the others fixed. An odds ratio of 1.4 means odds 40% higher, one of 0.6 odds 40% lower, one equal to 1 no effect."}}, {"@type": "Question", "name": "How do we get predicted probabilities from a logistic regression?", "acceptedAnswer": {"@type": "Answer", "text": "By applying the logistic function to the linear combination of the coefficients: p = 1 / (1 + e^−(b0 + b1x1 + … + bkxk)). In R, predict(model, newdata, type = \"response\") returns the probabilities directly; without type = \"response\" it returns log-odds."}}, {"@type": "Question", "name": "What if there are more than two categories?", "acceptedAnswer": {"@type": "Answer", "text": "If the categories have no order (for example desktop, mobile and tablet), we use multinomial logistic regression, in R with multinom() from the nnet package. If they have a natural order (low, medium, high), we use ordinal logistic regression, in R with polr() from the MASS package."}}, {"@type": "Question", "name": "What is the right threshold for classifying?", "acceptedAnswer": {"@type": "Answer", "text": "It depends on the cost of the two kinds of error and on how frequent the event is. With rare events, such as conversions, a 0.5 threshold often leads to never flagging anything. It's worth looking at the prediction table for several thresholds, or at the AUC to judge the model independently of the threshold."}}, {"@type": "Question", "name": "How much data does a logistic regression need?", "acceptedAnswer": {"@type": "Answer", "text": "What matters most is the number of events, the cases in the less frequent category. A widespread rule of thumb asks for at least ten or so per predictor: with 50 conversions, better not to go beyond four or five variables."}}]}</script>



<h2 class="wp-block-heading" id="try-it-yourself">Try it yourself</h2>



<p class="wp-block-paragraph">The article&#8217;s code lends itself to a few experiments.</p>



<ul class="wp-block-list"><li>Try a 0.2 threshold instead of 0.1: how many conversions does the model catch, and with how many false alarms? Compare the precision with the one at 0.1.</li><li>Remove brand from the model (<code>converts ~ pages + mobile</code>) and look at the AUC on the test set: how much is the information about the starting query worth on its own?</li><li>Raise the true coefficient for pages from 0.35 to 0.7 in the simulation: how does the shape of the curve change, and what happens to the 0.5 threshold?</li></ul>



<p class="wp-block-paragraph">The last exercise shows well the link between the strength of the predictors and the difficulty of classification: a model with strong effects pushes the probabilities towards the edges, and there one threshold is as good as another.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">In our example the three variables are independent by construction: pages viewed don&#8217;t depend on the device, brand doesn&#8217;t depend on pages. In real life that&#8217;s almost never the case. People arriving from a search for the brand name also tend to browse more pages, and when predictors overlap the coefficients become unstable and hard to tell apart. That&#8217;s <em>multicollinearity</em>, and together with two other names that sound scarier than they deserve, <a href="https://www.gironi.it/blog/en/multicollinearity-heteroscedasticity-autocorrelation-three-difficult-sounding-concepts-explained-simply/">heteroscedasticity and autocorrelation</a>, it&#8217;s the next stop on our journey through regression.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading" id="further-reading">Further reading</h3>



<p class="wp-block-paragraph">To go further into logistic regression and classification, I recommend <a href="https://www.amazon.it/dp/1461471370?tag=consulenzeinf-21&amp;ascsubtag=logistic-regression" rel="nofollow sponsored noopener" target="_blank"><em>An Introduction to Statistical Learning</em></a> by James, Witten, Hastie and Tibshirani: its chapter on classification starts from exactly the question of why linear regression doesn&#8217;t fit a yes/no outcome, then walks through logistic regression, confusion matrices and thresholds, with R labs that let us reproduce every step.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.gironi.it/blog/en/logistic-regression-predicting-the-outcome-of-an-event/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
