Business

Analyzing the Lottery Numbers

In my country, there is a weekly lotto game where participants select 6 numbers from a pool of 37 and another number from a pool of 7. Let’s focus on the first part of the game and disregard the selection of one additional number from a pool of 7.

When it comes to lotteries of the form k/N, where k is the number of desired selections (in our case, 6) out of a total pool of N numbers (in our case, 37), a common question is whether each of these numbers has an equal chance of being part of the winning combination.

Let’s investigate this question.

I’ve gathered statistics from their website for 1609 drawings spanning from 2009 to 2023.

Subsequently, I converted the data from the CSV file into an object:

{
 '09/09/2023': [13, 17, 24, 30, 35, 37],
 '07/09/2023': [7, 17, 19, 25, 35, 37],
 '05/09/2023': [2, 3, 5, 9, 36, 37],
 '02/09/2023': [4, 12, 22, 27, 30, 34],
 '29/08/2023': [6, 8, 15, 19, 26, 31],
 '26/08/2023': [6, 7, 14, 21, 25, 34],
 '22/08/2023': [2, 6, 10, 23, 24, 29],
 ...
}

The key in the object corresponds to the date of the drawing, and the associated value is an array of the numbers that emerged as the winning combination for that specific drawing.

Afterward, I created an array containing all the numbers obtained from the drawings:

numbers = np.array(list(lotto.values())).flatten()

[13, 17, 24, 30, 35, 37,  7, 17, 19, 25, 35, 37,  2,  3,  5,  9, 36, ...]

Following that, I calculated the count of occurrences (frequency) for each value within the array:

count = np.bincount(numbers)[1:]

[268, 256, 257, 242, 255, 273, 247, 277, 260, 267, 289, 294, 
 271, 239, 254, 255, 263, 243, 246, 271, 265, 254, 252, 243, 
 291, 271, 258, 264, 275, 258, 251, 244, 263, 256, 267, 251, 264]

These results indicate that the number 1 was drawn 268 times, the number 2 was drawn 256 times, and so on.

It appears that the distribution of numbers in the lottery results is relatively even. To further confirm this, we can conduct a test to validate the evenness of the distribution.

To test the equiprobability of the N individual numbers, you can follow this approach:

  • Calculate the observed frequency (Oi) with which each number i = 1, …, N occurred in the n lottery draws.
  • Compute the expected counts (Ei) for each number using the formula Ei = (nk) / N, where n is the total number of lottery draws, k is the number of numbers selected in each draw (in this case, 6), and N is the total number of possible numbers (in this case, 37).
  • Use the Pearson statistic or chi-squared statistic to compare the observed counts (Oi) with the expected counts (Ei). The formula for the Pearson statistic is often expressed as:

  • Calculate the chi-squared statistic using the observed and expected counts.
  • Perform a statistical test, such as a chi-squared test, to determine if the computed chi-squared value is statistically significant. This will help you assess whether the distribution of numbers is significantly different from what would be expected under equiprobability.

If the calculated chi-squared value is not statistically significant, it suggests that the numbers are reasonably evenly distributed, supporting the hypothesis of equiprobability. However, if the X^2 value is significant, it would indicate a departure from equiprobability.

Let’s create a function to perform the chi-squared test for equiprobability of numbers:

def chi2(data, size, expect, p_value = 0.05):
  pl = size * 1/expect
  df = expect - 1

  x2_crit_1 = stats.chi2.ppf(p_value, df)
  x2_crit_2 = stats.chi2.ppf(1 - p_value, df)

  x2 = 0
  for i in range(expect):
    x2 += ((data[i] - pl) ** 2)/pl

  accepted = x2_crit_1 < x2 < x2_crit_2 if x2_crit_1 < x2_crit_2 else x2_crit_2 < x2 < x2_crit_1

  return x2, accepted

This function returns the tuple consisting of the chi-squared statistic and result that equiprobability accepted with the probability 1 - 2 * p-value , i.e., extreme values of this discrete uniform distribution have low probability.

N = 37

chi2(count, len(numbers), N)

(25.183136523720748, True)

Certainly, you can use the built-in functionality from the SciPy library to perform the chi-squared test for equiprobability:

from scipy import stats

chi2_statistic, p_value = stats.chisquare(count)

(25.18313652372074, 0.9115057832606053)

Let’s explore combinations of these numbers, starting with pairs:

from itertools import combinations

pairs = list(combinations(range(1, N), 2))

Following this step, we construct a 2D matrix that tracks the occurrences of these pairs:

pairs_count = np.zeros([N] * 2, dtype=int)

for pair in pairs:
  for draw in lotto.values():
    if pair[0] in draw and pair[1] in draw:
      pairs_count[pair[0]][pair[1]] += 1

pairs_count = pairs_count[1:, 1:]

This forms a triangular matrix, as it accounts for the fact that pairs (a, b) and (b, a) are equivalent, and we only tally the occurrences of the pairs (a, b).

My function yields:

counts = pairs_count.flatten()
counts = counts[counts > 0]

chi2(counts, sum(counts), len(counts))

(589.2721893491138, True)

and SciPy provides:

chi2_statistic, p_value = stats.chisquare(counts)

(589.2721893491124, 0.8698507423203673)

How about considering triplets:

comb3 = list(combinations(range(1, N), 3))

comb3_count = np.zeros([N] * 3, dtype=int)

for comb in comb3:
  for draw in lotto.values():
    contains = comb[0] in draw and comb[1] in draw and comb[2] in draw
    if contains:
      comb3_count[comb[0]][comb[1]][comb[2]] += 1

comb3_count = comb3_count[1:, 1:, 1:]

counts = comb3_count.flatten()
counts = counts[counts > 0]

chi2(counts, sum(counts), len(counts))

(6457.575829383709, False)

Something has gone awry, possibly due to the matrix’s high sparsity. The chi-squared value falls below the lower critical chi-squared threshold:

6457.575829383709 < 6840.049842653838

However, when using SciPy, the result is:

chi2_statistic, p_value = stats.chisquare(counts)

(6457.575829383886, 0.9999997038479482)

Now, let’s identify the number that has been drawn most frequently:

count.argmax() or list(count).index(max(count))

11

Let’s not jump to conclusions just yet. We can examine how this number has evolved over the years:

year_result = dict()
for year in range(2009, 2024):
  new_dict = {k:v for (k,v) in lotto.items() if str(year) in k}
  year_result[year] = np.bincount(np.array(list(new_dict.values())).flatten())[1:].argmax()

{
 2009: 16,
 2010: 10,
 2011: 11,
 2012: 24,
 2013: 32,
 2014: 34,
 2015: 21,
 2016: 25,
 2017: 5,
 2018: 10,
 2019: 24,
 2020: 11,
 2021: 12,
 2022: 14,
 2023: 11
}

Or, alternatively, we can analyze the cumulative changes over time:

year_result = dict()
arr = []
for year in range(2009, 2024):
  new_dict = {k:v for (k,v) in lotto.items() if str(year) in k}
  arr += list(np.array(list(new_dict.values())).flatten())
  year_result['2009 - ' + str(year) if year > 2009 else str(year)] = np.bincount(arr)[1:].argmax()

{
        '2009': 16,
 '2009 - 2010': 10,
 '2009 - 2011': 11,
 '2009 - 2012': 20,
 '2009 - 2013': 20,
 '2009 - 2014': 20,
 '2009 - 2015': 34,
 '2009 - 2016': 20,
 '2009 - 2017': 10,
 '2009 - 2018': 10,
 '2009 - 2019': 10,
 '2009 - 2020': 10,
 '2009 - 2021': 10,
 '2009 - 2022': 24,
 '2009 - 2023': 11
}

Lastly, we can also investigate whether identical drawings have ever occurred:

lotto_counts = {}
for k, v in lotto.items():
  v_str = str(v)
  if v_str in lotto_counts:
     lotto_counts[v_str] += [k]
  else:
     lotto_counts[v_str] = [k]

result = {k: v for k, v in lotto_counts.items() if len(lotto_counts[k]) > 1}

{
  '[13, 14, 26, 32, 33, 36]': ['16/10/2010', '21/09/2010']
}

It’s amusing to note that these occurrences happened nearly back-to-back.

As we wrap up our journey into the world of lottery data, it’s been a wild ride through numbers and probabilities. We’ve uncovered some interesting tidbits – from pairs and triplets to spotting the most popular numbers.

Lotteries are all about unpredictability, but it’s fun to peek behind the curtain and explore the quirks of these games. Whether you’re a player or just a curious observer, the world of numbers always has a surprise or two up its sleeve.


This article was originally published by Alexthoughts on Hackernoon.

HackerNoon

Recent Posts

Competing in the post-gatekeeper era: How the DMA is rewiring platforms, security, and market access

The Digital Markets Act (DMA) has joined the General Data Protection Regulation (GDPR) as one…

2 days ago

Horasis India Meeting to Spotlight India’s Global Ascent At Singapore Summit This Month

Amid several years of shifting global dynamics, it’s become increasingly clear that we are entering…

3 days ago

AI scams targeting businesses are surging: Here are the top 3 threats your team is likely to face in 2026 (Brains Byte Back Podcast)

Imagine a company interviewing a candidate for a senior IT role. The résumé checks out,…

3 days ago

AI Won’t Scale in Advertising Until Trust Does: How to Identify AI Tools That Deliver Quality Security and Expertise

At the start of the year, data suggested that only about a third of agencies,…

3 days ago

What It Means When Algorithms Say “I”: Toward a Theory of Digital Subjectivity

Picture an AI assistant you have worked with for the past five years. It knows…

4 days ago

Why One of the Oldest Coding Languages Still Outsmarts Modern AI

They tried to kill it. To bury it for good. But every time, it clawed back — stronger,…

4 days ago