The Distribution of French Firm Sizes#
Chapter 8 of Probably Overthinking It is called “The Long Tail of Disaster” because it looks at the distribution of magnitudes for earthquakes, solar flares, asteroid impacts, and stock market crashes, among other kinds of disaster.
In the chapter, I compared two models of these distributions, lognormal and log-t, which are normal and Student t distributions on a log scale. The point of the chapter is this:
If you look at the tail of these distributions on a linear scale, the lognormal model seems like a good fit.
But if you look on a log-y scale, it is visibly clear that the lognormal model underestimates the probability of very large disasters – in some cases by several orders of magnitude.
The log-t model looks better: it fits the observed distributions well for both moderate and extreme values, which suggests that it’s predictions for the largest disasters are more reliable.
Here you can read the notebook that contains all of the analysis.
When I was writing the book, I planned to have a chapter on other distributions with similar tail behavior, including the sizes of cities (by population), companies (both by revenue and number of employees) TODO… more
I ended up cutting the chapter for a few reasons:
It’s hard to get good quality datasets that are large enough to show the tail behavior,
In some of the datasets I explored, the tail behavior is complicated, and
It’s less obvious for these examples that the tail behavior matters.
But I keep an eye out for datasets to test, and I found one recently. In France, companies file their annual accounts with the commercial courts, and the Institut National de la Propriété Industrielle (INPI) publishes the non-confidential accounts as open data. That includes private companies, not just publicly traded ones, and it reports revenue for individual companies – unbinned – all the way up to the largest firms in the country.
So let’s take a look. To characterize the tail behavior, I’ll fit the same models I used in the book, lognormal and log-\(t\). And I’ll add two more, log-generalized-normal and a Pareto distribution, which is a common choice for data like this.
TODO: summarize the results
Click here to run this notebook on Colab.
Data#
The data are from INPI, Registre national des entreprises (RNE), comptes annuels, bulk stock file of 2026-03-20, accessed 2026-10-15, and reused under the RNE reuse licence. This reuse is not endorsed by INPI.
I made an extract with one row per filing and a few variables: fiscal year, length of the fiscal year, type of accounts, confidentiality status, industry, revenue, total assets, and average number of employees.
The extract does not include company identifiers or names, and the results below are aggregate.
The script that makes the extract is extract_inpi.py.
The extract includes fiscal years from 2016 through 2025, with about 700,000 filings per year.
I’ll use fiscal year 2023, the most recent year that is complete.
There are three types of accounts: complete (C), simplified (S) for smaller companies, and consolidated (K) for groups of companies.
Consolidated accounts include the revenue of subsidiaries that also file their own accounts, so I’ll drop them to avoid counting the same revenue twice.
I’ll also keep only fiscal years that are 12 months long, so revenue is comparable across companies.
Who is missing#
Companies can choose to keep part of their accounts confidential, including their income statement, which is where revenue is reported. Here’s how many filings are public and how many are partly confidential.
About a third of filings are partly confidential, so they don’t report revenue. That would not be a problem if confidentiality were unrelated to size, but it isn’t – under French law, only small companies can keep their income statement confidential, and the smallest (“micro”) companies can keep their entire accounts confidential.
Confidential filings still report total assets, so we can see how the fraction of confidential filings depends on size.
Among companies with total assets between about €400,000 and €5 million, a third to a half of filings are partly confidential. Above €30 million, fewer than 5% are. The jump near €350,000 is probably the threshold for micro companies: below it, a company that wants confidentiality can withhold its entire accounts, so it doesn’t appear here as partly confidential – or, possibly, at all.
So the companies we can see are a biased sample of companies with revenue below roughly €20 million, and a nearly complete sample above. Companies that file with zero revenue (many are holding companies) are also excluded, and they are mostly small, too.
To avoid modeling the selection process instead of the distribution of firm sizes, I’ll fit the models only to companies above a lower cutoff. The choice of cutoff is a modeling decision; I’ll use €10 million and check later how much the results depend on it.
Distribution of revenue#
Here’s the CDF of revenue for all companies with positive revenue, with a log-\(x\) axis. The vertical line shows the cutoff.
On a log-\(x\) scale, the CDF has the sigmoid shape of a normal distribution, so the lognormal model is a natural candidate.
Now I’ll select companies above the cutoff.
That’s about 10% of the companies that report positive revenue. Here are some summary statistics.
Log revenue#
As in other examples, I’ll work with the logarithm of revenue, base 10. A normal distribution of log revenue is a lognormal distribution of revenue.
Fitting truncated models#
Because we only use data above the cutoff, each model has to be truncated at the cutoff, too.
The functions that fit the models are in tail_models.py, so the other firm-size examples can use them, too.
truncated_tailevaluates the tail distribution of a SciPy distribution on a grid of quantities, normalized so the probability at the low end of the grid is 1.To fit a model, we choose a set of percentiles and find the parameters that minimize the vertical distances between the data and the model at those percentiles. For a model with a shape parameter, like the Student-\(t\) distribution,
fit_loc_scalefinds the location and scale for a given value of the shape parameter.Then
fit_shapesearches for the value of the shape parameter that best matches the largest values in the data – specifically, it minimizes the squared differences between the logarithms of the tail probabilities, for probabilities from 1% down to the second-largest value.plot_twoplots the data and a model on a linear-\(y\) scale and a log-\(y\) scale, side by side. On the log-\(y\) scale, the shaded area shows how much variation we would expect due to sampling if the model were correct.
To compare models, I’ll compute two kinds of error with model_errors:
The mean absolute difference between the tail distributions of the data and the model, on the linear scale, at 99 percentiles from 1% to 99%. That’s mostly sensitive to discrepancies among most of the data.
The root mean squared difference between the logarithms of the tail probabilities, at 30 points from the 99th percentile to the second-largest value. That’s mostly sensitive to discrepancies among the largest companies.
I’ll evaluate all models on the same grid, extending a little beyond the largest value.
Lognormal model#
For the lognormal model, I fit a normal distribution to log revenue, truncated at the cutoff. As in the previous example, I match percentiles from the 10th to the 99th.
On the linear-\(y\) scale, the lognormal model fits the data well. On the log-\(y\) scale, we can see that it doesn’t: above about €300 million, the data are well above the model, and the discrepancy grows to an order of magnitude or more for the largest companies – far outside the range we would expect from random sampling. The lognormal model does not fit this distribution.
Log-Student-\(t\) model#
The Student-\(t\) distribution has a parameter, df, that controls the thickness of the tails.
With large values of df, it is nearly normal; with small values, it has much thicker tails.
A Student-\(t\) distribution of log revenue can only have thicker tails than the lognormal model.
The log-\(t\) model fits the data well on both scales, with df about 12.
On the log-\(y\) scale, the data are within or close to the range we would expect from random sampling.
Log-generalized-normal model#
The generalized normal distribution has a shape parameter, \(\beta\), that can make the tails thicker or thinner than normal: \(\beta = 2\) is the normal distribution, \(\beta < 2\) has thicker tails, and \(\beta > 2\) has thinner tails. So the lognormal model is a special case of the log-generalized-normal model, and the fitted value of \(\beta\) shows which way the data depart from it, if they do.
The fitted value of \(\beta\) is about 1.2, well below 2, so the departure from the lognormal model is toward thicker tails. The log-generalized-normal model also fits well on both scales, with the data mostly inside the range we would expect from random sampling.
Pareto model#
A Pareto distribution has a tail distribution \(P(X \ge x) = (x / x_m)^{-\alpha}\), which is a straight line with slope \(-\alpha\) on log-log axes. With \(\alpha = 1\), it is a Zipf distribution.
Truncated at the cutoff, a Pareto distribution has only one parameter, \(\alpha\), so it can’t bend near the cutoff. A more flexible version is the Pareto II (or Lomax) distribution, which has a scale parameter, \(s\), as well as the exponent:
where \(c\) is the cutoff. For values much larger than \(c + s\), it behaves like a Pareto distribution with exponent \(\alpha\); near the cutoff, the scale parameter controls how it bends.
I’ll fit it the same way as the other models with a shape parameter: given \(\alpha\), fit_pareto2_scale finds the scale that matches percentiles of the data, and fit_pareto2_alpha searches for the value of \(\alpha\) that best matches the largest values in the data, using the same criterion as fit_shape.
pareto2_tail computes the tail distribution of log revenue under this model.
The fitted exponent is about 1.13, and the scale is about €17 million. The Pareto II model fits the data well on both scales. On the log-\(y\) scale, the data are mostly within the range we would expect from random sampling; the handful of largest companies are a little below the model, near the edge of that range.
Comparison#
Here are all four models on a log-\(y\) scale.
And here are the errors.
| Mean abs error | RMS log error | |
|---|---|---|
| lognormal | 0.0030 | 0.9124 |
| log-t | 0.0017 | 0.0796 |
| log-gennorm | 0.0032 | 0.0559 |
| Pareto II | 0.0013 | 0.0604 |
The log-\(t\), log-generalized-normal, and Pareto II models all fit well, and their curves are hard to tell apart except beyond the largest companies, where there are no data. The Pareto II model has the smallest error on the linear scale, and the log-generalized-normal and Pareto II models have the smallest errors on the log scale. The lognormal model has a small error on the linear scale, but a large error on the log scale.
Robustness#
To see whether these results depend on the year or the cutoff, I’ll fit the lognormal, log-generalized-normal, and Pareto II models for each fiscal year from 2016 through 2024, and for several cutoffs. The following function selects the data.
fit_models, in tail_models.py, fits the lognormal, log-generalized-normal, and Pareto II models to one dataset and returns the parameters and errors.
Here are the results for each year, with the €10 million cutoff.
| n | beta | alpha | log_scale | rmse_lognormal | rmse_gennorm | rmse_pareto | |
|---|---|---|---|---|---|---|---|
| 2016 | 26218.0 | 1.138 | 1.101 | 7.129 | 0.396 | 0.041 | 0.053 |
| 2017 | 29576.0 | 1.169 | 1.121 | 7.149 | 0.362 | 0.046 | 0.064 |
| 2018 | 30310.0 | 1.175 | 1.139 | 7.163 | 0.403 | 0.046 | 0.064 |
| 2019 | 31851.0 | 1.177 | 1.137 | 7.173 | 0.540 | 0.047 | 0.062 |
| 2020 | 30133.0 | 1.191 | 1.156 | 7.193 | 0.639 | 0.044 | 0.067 |
| 2021 | 31967.0 | 1.174 | 1.125 | 7.199 | 0.873 | 0.060 | 0.064 |
| 2022 | 34212.0 | 1.204 | 1.133 | 7.216 | 0.749 | 0.044 | 0.056 |
| 2023 | 34620.0 | 1.200 | 1.133 | 7.230 | 0.912 | 0.056 | 0.060 |
| 2024 | 32122.0 | 1.206 | 1.121 | 7.241 | 0.990 | 0.062 | 0.071 |
In every year, the log-generalized-normal and Pareto II models fit well, and the lognormal model does not. The fitted parameters are consistent from year to year: \(\beta\) is between 1.14 and 1.21, and the Pareto exponent is between 1.10 and 1.16.
And here are the results for fiscal year 2023 with several cutoffs.
| n | beta | alpha | log_scale | rmse_lognormal | rmse_gennorm | rmse_pareto | |
|---|---|---|---|---|---|---|---|
| €1,000,000 | 125111.0 | 1.723 | 1.047 | 6.489 | 0.199 | 0.092 | 0.110 |
| €3,000,000 | 72306.0 | 1.539 | 1.091 | 6.847 | 0.312 | 0.097 | 0.076 |
| €10,000,000 | 34620.0 | 1.200 | 1.133 | 7.230 | 0.912 | 0.056 | 0.060 |
| €30,000,000 | 14380.0 | 1.103 | 1.142 | 7.574 | 0.258 | 0.054 | 0.061 |
| €100,000,000 | 4354.0 | NaN | 1.147 | 8.047 | inf | inf | 0.063 |
The log-generalized-normal results depend on the cutoff: as the cutoff increases, \(\beta\) decreases, from about 1.7 with the €1 million cutoff to about 1.1 with the €30 million cutoff. With the €100 million cutoff, the lognormal and log-generalized-normal fits don’t converge: the best-fitting location moves farther and farther below the cutoff, so the part of the model above the cutoff approaches an exponential distribution of log revenue – which is a Pareto distribution of revenue.
The Pareto II results hardly depend on the cutoff: the exponent is between 1.05 and 1.15 for all five cutoffs, and the log-scale error is small for all of them. The scale parameter moves with the cutoff, a little above it in each case, which is where the model bends.
So for every cutoff from €1 million to €100 million, the distribution of revenue above the cutoff is consistent with a Pareto II distribution with exponent a little above 1. The fit is not as good with the €1 million cutoff, where the selection process has the most effect.
Employment#
The data also include the average number of employees, but only some companies report it.
Larger companies are more likely to report employment, but even among companies with revenue above the cutoff, many don’t.
And some of the values that are reported are clearly wrong. Here are the number of filings that report at least 10,000 and at least 100,000 employees.
The largest companies in France employ a few hundred thousand people, so most of these can’t be right. Revenue per employee shows the problem: among filings that report 10,000 or more employees, the median is a few hundred euros per employee, compared to about €200,000 for companies with 50 to 1,000 employees.
Errors like these are concentrated among the largest values, which is the part of the distribution we care about most. So I won’t fit models to employment.
Summary#
Above a cutoff of €10 million, where the selection process has little effect, the lognormal model does not fit the distribution of revenue: the largest companies are far more common than it predicts.
A Pareto II model with exponent about 1.13 fits the data well on both the linear and log scales, and so do a log-Student-\(t\) model and a log-generalized-normal model with \(\beta\) about 1.2.
The Pareto II model is the most consistent: its exponent is between 1.05 and 1.16 for every year from 2016 through 2024 and for every cutoff from €1 million to €100 million. The fitted shape of the log-generalized-normal model depends on the cutoff, and it doesn’t fit at all above €100 million.
Employment data in these filings have errors that are concentrated among the largest values, so I did not model them.
Copyright 2026 Allen B. Downey
Data: INPI – Registre national des entreprises, comptes annuels (stock of 2026-03-20, accessed 2026-10-15), reused under the RNE reuse licence.
Example related to Chapters 4 and 8 from Probably Overthinking It.
License: Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)