유랑하는 나그네의 갱생 기록

だけど素敵な明日を願っている -HANABI, Mr.children-

Study/Python

[Python] 로또 번호 분석으로 추첨번호를 만들어낼 가치가 있을까 (feat. 스피또, 트리플럭)

Madirony 2024. 12. 29. 07:55
728x90

뭐 이딴 번호가 다있니..

 최근 일확천금으로 억만장자가 되자! 프로젝트를 진행하고 있습니다. 거창한 건 아니구요.
"코인이나 주식을 하려니 시드 머니가 없는뎁쇼 ?-?"

 

... 그러면 시드 머니를 만들면 되는 거 아닌가?

 

ㅇㅇ...

 

 

그런데 경마장 가서도 한 번도 못 딴 사람이 운으로만 승부하는 도박판에서 이길 수 있는 확률이 얼마나 될까요..? 유난히 소소한 행운은 잘 따라주는 편이지만, 각 잡고 하는 도박에는 영 소질이 없습니다. 소소한 행운이라 하면 에피소드야 많습니다. 게임 스타팅 뽑기라던가 한정 가챠 운이 좋아서 상위 랭크까지 찍고 접은 모바일 게임들이 많았습니다.

 

가장 기억에 남는 것은 학부생 1학년 때 "4년치 학생회비를 내지 않으면 행사에 참여할 수 없다"는 소리에 끝까지 안 내고 버티고 있었지만, 신입생 환영회 당일에 명단을 불러서 약 27만원을 뜯기고야 말았습니다. 안 낼 수도 있었기야 했는데 BTS 단체 안무를 연습해 둔 상황이라 제가 또 빠지면 안돼서요..(3등해서 10만원 받음) 하지만 그 이후로 총학생회 주관 랜덤 이벤트에서 에어팟에 당첨되었습니다.

 

이런 쪽박 소박 중박 같은 행운 말고 대박을 한번 잡고 싶긴 합니다. 그래서 제일 먼저 한 게 무엇이냐..

 

 

 

.

.

.

 

 

TRIPLE LUCK 🎰

트리플럭

..트리플럭입니다. 온라인 스피또라고도 할 수 있죠? 얼마.. 얼마를 꼴았냐구요..

 

 

 

......

 

 

아니 ** 내 50,000원이!!!!!

 

당첨내역

일단 50,000원으로 13,000원을 만들어내는 기적을 행했습니다만. 대체 기댓값이 어떻게 되길래 이런 결과가 나왔을까요?

 

기본 당첨구조

 

보너스 당첨금 구조

아.. 보너스 확률이 더 처참하네요. 일단 기댓값을 계산하기 위해서는 각 등위의 기댓값을 구한 뒤 합산하는 식으로 구해봅시다.


(기본 기댓값) = 133.33 + 53.33 + 16.67 + 13.33 + 3.33 + 10.00 + 60.06 + 300.30 = 590.35원

(보너스 기댓값) = 2.67 + 4.00 + 3.60 = 10.27원

(총 기댓값) = 590.35 + 10.27 = 600.62원

 

저는 50장을 샀으니 600.62 * 50 = 30,031원이 기댓값입니다. 여기에 또 당첨금을 써서 7게임을 더 했거든요..? 8등 2번이 더 나왔으니까 다시 계산해 보면.. 34,235.34원이 최종 기댓값입니다. 약 19,235원만큼 더 손해를 봤네요. 아니 애초에 기댓값 자체가 손해인 구조긴 한데, 그거보다도 못 나왔으니..

 

...?

그런데 그 와중에 25년부터는 판매수량과 판매율을 비공개한다고 합니다. 스피또 같은 건 특정 판매율을 달성할 때마다 1등 물량을 푼다는 소문을 한 번씩은 들어봤을 텐데 그런 것 때문에 "특정시기에 구매" 쏠림 현상이 나타나는 것 같습니다. 그러다 보니까 최근에도 50% 구간에 5억이 나오기도 했고.. 합리적인 의심일 수도 있긴 한데 알고리즘을 뜯어보지 않는 이상 일개 개인이 어떻게 알겠어요.

 

근데 저라면 그렇게 알고리즘을 짰을 거 같습니다. 판매량 기반으로 1등 당첨자를 결정한다던가, 당첨 확률을 유동적인 형태로 만든다던가, 특정 유저는 락을 걸어서 특정 등수 이상으로는 당첨되지 않게 한다던가.

 

 

아잇~

이런 건 이제 그만할까- 싶었습니다.

 

 


스피또 1000 💯

근데 왜 스피또에는 손을 대셨어요.. 라고 묻는다면,

 

딱지남

공유 아저씨가 빵과 복권을 선택할 기회를 주었습니다.. 대부분의 노숙자들이 그랬듯이 저 역시 빵을 포기하고 복권을 선택했죠.

 

빵 50개.. / 확률표

기회는 50번, 기댓값은..

(1장당 기댓값)= 100 + 20 + 55 + 125 + 303 = 603원

50장이니까 30,150원.

 

...

5등 15장, 4등 1장이 나왔습니다. 20,000원. 로또 4등은 여러 번 해봤는데 즉석 복권이나 연금 복권은 결과가 별로 좋지 않네요. 게다가 동전없이 긁으니까 손가락만 아픔. 스피또는 출고율이랑 내 지역에 1등 물량이 들어오는 행운이 있어야 하는 특수한 상황까지 고려해야 해서 어지간한 운 아니면 못 건지는 거 같아요. 그냥 내일쯤 복권방 가서 교환하고 올 예정입니다.

 

 

 

 

 

어유 아까워 어유

저는 선택을 했습니다.. 50개의 빵을 바닥에 버리는 선택을요..

 


로또 6/45 🍀

로또 확률표

이번주 로또 당첨번호가 30, 31, 32, 35, 36, 37이라 1등이 많이 배출되었습니다. 그중에서 한 사람이 같수오를 해서 인생 역전을 하셨죠. 아니 어떻게 번호가 이렇게 나올 수가 있나. 거기다가 이런 번호를 찍는 사람은 대체 뭘까 싶기도 했습니다. 그런데 1, 2, 3, 4, 5, 6 같은 번호의 경우만 봐도 이번 회차랑 확률은 같죠. 그냥 로또는 어떤 수의 조합이냐에 따라 확률이 바뀌지 않으며 이전 회차의 결과에 영향을 받지 않는 독립시행입니다.

 

그럼에도 불구하고,

"로또 번호는 예측이 가능한가?"라는 질문도 자연스럽게 따라옵니다. 여기에 관한 논문이나 연구는 많이 있긴 합니다만. 마르코프 체인이라는 확률 모델을 사용해서 번호 간의 관계성을 평가를 했는데, 반복적으로 등장하는 숫자나 특정 조합에 대한 확률이 발견되긴 했지만 정확한 번호를 100% 예측할 수 없다는 한계를 언급하기도 했습니다. 또한 1등 당첨번호에 대한 무작위성을 검정한 결과, 1등 당첨번호들은 무작위성을 따른다는 결론을 내렸습니다.

 

 

"하.. 그러면 로또 번호 추첨 프로젝트나 서비스들은 대체 무슨 소용이야!"

 

 

 

It's faker..

맞아요. 그런 프로젝트들은 의미가 없습니다. 의미가. 정말 그런 게 쓸모가 있나요? 물론 통계적 확률을 기반으로 번호를 고르는 전략을 세울 수도 있겠죠. 최근 10회 동안 빈번히 등장한 번호를 몇 개 추리거나 오랫동안 나오지 않은 번호를 위주로 추첨 번호를 생성하는 "전략" 말이에요.

 

로또 역시 추첨기에 조작을 가한다거나 허수 당첨자를 생성한다거나 하는 의혹은 있을 수도 있습니다. 만에 하나 그런 일이 있다 하더라도 실제 당첨자는 하나둘씩 있겠죠. 저는 그걸 노리는 겁니다. 대놓고 조작할 거면 아무도 고르지 않은 번호 하나에 몰빵 해주는 식으로 하겠죠.

 

1등 할 수 있는 로또 번호를 생성해 주겠다는 소리는 마치.. 불확실성(+무작위성) 요인이 난무하는 코인, 주식 시장에서 완벽한 예측 시스템을 만들겠다는 소리와 같습니다. 그런 게 있으면 혼자 써야지 누가 공유를 할까요.. 너도 나도 그런 걸 쓰는 시점에서 의미가 없어지는데.

 

601회차부터 1152회차까지의 데이터를 분석해 보자!

동행 복권 홈페이지에 들어가면 여태까지의 당첨번호 데이터를 엑셀 혹은 html 형태로 다운로드할 수 있습니다. 아주 오래전 로또는 1게임 당 2000원할 때도 있어서 601회차부터 확인해 보도록 하겠습니다. 사실 홈페이지에서도 600회차 단위로 데이터를 끊습니다. 그래도 약 10년 치의 데이터니 충분하다고 봅니다. 원래 제 주력 언어는 Java이긴 하지만 데이터 시각화와 분석 쪽은 Python이 맛있어서 오랜만에 써봤습니다.


 

[CSV 파싱]

 

csv 변환

Mac OS라 그래서 그런진 몰라도 한글 인코딩이 깨져있고 파일이 또 xls 확장자라 csv로 변환하는 과정을 거쳤습니다. 엑셀 파일이긴 하지만 까보면 그냥 html을 엑셀로 옮긴 거라 문제가 있었습니다. 그래서 뭘 써야 한다? BeautifulSoup를 써야 한다~입니다. html에서 데이터를 파싱해서 csv로 변환시켰습니다.


[번호 빈도수 시각화]

 

# 당첨 번호를 개별 컬럼으로 분리
df[['num1', 'num2', 'num3', 'num4', 'num5', 'num6']] = df['Winning Numbers'].str.split(', ', expand=True).astype(int)
df['bonus'] = df['Bonus Number'].astype(int)

# 필요한 컬럼만 선택
df = df[['Round', 'num1', 'num2', 'num3', 'num4', 'num5', 'num6', 'bonus']]

# 모든 번호의 빈도수 계산
all_numbers = df[['num1', 'num2', 'num3', 'num4', 'num5', 'num6']].values.flatten()
number_counts = pd.Series(all_numbers).value_counts().sort_index()

# 최빈값 & 희소값 번호 시각화
plt.figure(figsize=(12, 6))
sns.barplot(x=number_counts.index, y=number_counts.values)
plt.title('Frequency of Lotto Numbers (Sparse Values)')
plt.xlabel('Number')
plt.ylabel('Frequency')
plt.show()

그 후, 파일을 로드하고 데이터를 적절하게 전처리 해주었습니다. 1-45 모든 번호의 빈도수를 모아서 시각화하기 위함이었죠.

 

 

로또 번호 빈도 그래프

.. 여기서 유의미한 값을 뽑아낼 수 있을까요? 빈도가 낮은 숫자인 5, 9, 20, 25, 32에서 숫자 하나를 골라서 추첨 번호에 섞어본다고 칩시다.

 

 

최근 5회차

최근 5회차의 당첨번호입니다. 뭐, 일부는 포함되긴 하지만 나머지 수는 어떻게 맞추려구요? ¯\_(ツ)_/¯

 

 

이번회차 결과

참고로 저도 이번 논란의 회차 당첨번호 중에서 2개나 맞추긴 했습니다. 하지만 5등도 하지 못했죠. 20, 21이 아니라 30, 31이었으면 5만원은 받긴 했겠네요. (하.. 내 스피또 **..) 그런데 그렇게 따지면 끝도 없습니다.


[연번 회차 분석]

 

# 연속된 번호가 포함된 회차 수 계산
def find_consecutive_numbers(numbers):
    numbers = sorted(numbers)
    consecutive_numbers = []
    current_consecutive = [numbers[0]]
    for i in range(1, len(numbers)):
        if numbers[i] == numbers[i - 1] + 1:
            current_consecutive.append(numbers[i])
        else:
            if len(current_consecutive) > 1:
                consecutive_numbers.append(current_consecutive)
            current_consecutive = [numbers[i]]
    if len(current_consecutive) > 1:
        consecutive_numbers.append(current_consecutive)
    return consecutive_numbers

df['consecutive_numbers'] = df.apply(lambda row: find_consecutive_numbers([row['num1'], row['num2'], row['num3'], row['num4'], row['num5'], row['num6']]), axis=1)

# 2연번부터 6연번까지 포함된 회차 수 계산
consecutive_counts = df['consecutive_numbers'].apply(lambda x: max([len(c) for c in x], default=0)).value_counts().sort_index()
for i in range(2, 7):
    if i not in consecutive_counts:
        consecutive_counts[i] = 0

consecutive_counts = consecutive_counts.sort_index()

print("Number of rounds with consecutive numbers:")
for i in range(2, 7):
    print(f"{i} consecutive numbers: {consecutive_counts[i]} rounds")

# 연번의 종류 출력
print("\nDetails of consecutive numbers in each round:")
for index, row in df.iterrows():
    if row['consecutive_numbers']:
        consecutive_details = {len(c): c for c in row['consecutive_numbers']}
        details_str = ', '.join([f"{len(c)}{{{', '.join(map(str, c))}}}" for c in row['consecutive_numbers']])
        print(f"Round {row['Round']}: {details_str}")

# 시각화
plt.figure(figsize=(10, 6))
sns.barplot(x=consecutive_counts.index, y=consecutive_counts.values)
plt.title('Number of Rounds with Consecutive Numbers')
plt.xlabel('Number of Consecutive Numbers')
plt.ylabel('Number of Rounds')
plt.show()

이번엔 연속된 번호가 포함된 회차를 분석해 보았습니다. 2연속-6연속의 번호의 회차 수를 계산하고 연번의 종류와 구성을 출력하도록 하였습니다.

 

 

연번 회차의 수

생각보다 의외의 결과였습니다. 연번이 아닌 조합보다 연번인 조합의 수가 훨씬 많았으니까요. 그야 숫자가 45개 밖에 없어서 2연번의 조합은 많이 나오긴 하겠지만요.

 

 

연번 조합의 구성

2연번 3세트, 2연번+3연번 세트, 4연번 등 다양한 조합의 수가 있었습니다. 특히 655회차는 어릴 적에 로또 번호를 분석하던 시절이 있었는데 4연번은 나오지 않을 거 같으니 그러한 경우의 수를 모두 제외한 번호 조합을 만들면 되겠다는 생각을 깨부쉈던 회차였습니다. 그런데 이번 3연번+3연번 세트도 심상치 않은 조합이라.. 올해부터 로또 구매 방식에 변화를 주었는데 이 방식에 더 확고해졌습니다.


[이전 회차 영향 분석 - 1]

 

# 이전 회차의 번호가 다음 회차에 끼치는 영향 분석
df['next_round'] = df['Round'].shift(-1)
df_next = df.dropna(subset=['next_round'])

def analyze_number_relationships(row):
    current_numbers = {row['num1'], row['num2'], row['num3'], row['num4'], row['num5'], row['num6']}
    next_numbers = df.loc[df['Round'] == row['next_round'], ['num1', 'num2', 'num3', 'num4', 'num5', 'num6']].values.flatten()
    next_numbers = set(next_numbers)
    
    same_numbers = current_numbers & next_numbers
    
    return len(same_numbers), list(same_numbers)

df_next[['same_numbers', 'same_numbers_list']] = df_next.apply(lambda row: analyze_number_relationships(row), axis=1, result_type='expand')

# 동일한 번호의 개수 시각화
plt.figure(figsize=(10, 6))
sns.histplot(df_next['same_numbers'], bins=range(8), kde=False, discrete=True)
plt.title('Number of Matching Numbers Between Consecutive Rounds')
plt.xlabel('Number of Matching Numbers')
plt.ylabel('Frequency')
plt.show()

# 이전 회차와 3개 이상 일치하는 회차 출력
print("\nRounds with 3 or more matching numbers:")
df_next_sorted = df_next.sort_values(by='Round')
for index, row in df_next_sorted[df_next_sorted['same_numbers'] >= 3].iterrows():
    current_round = int(row['Round'])
    next_round = int(row['next_round'])
    same_numbers_list = list(map(int, row['same_numbers_list']))
    print(f"{current_round},{next_round} : {same_numbers_list}")

우연의 일치겠지만, 이전 회차에서 등장했던 번호가 다음 회차에 재등장하는 경우를 분석했습니다. 겹치지 않는 경우도 있긴 하지만 연번 경우와 유사한 형태를 띠고 있습니다.

 

 

이전 회차의 번호가 다음 회차 번호에 등장하는 경우

이전 회차의 번호 중에서 하나를 선택해서 다음 회차 번호 조합에 섞는다면 그러지 않았을 때보다는 좀 더 유의미한 조합으로 기대를 가질 수도 있겠습니다. 하지만 독립시행이라는 거 잊으면 안 되겠죠? ^0^

 

 

중복해서 등장한 수

하지만 제가 로또 번호에 공을 들이던 시절에는 그러한 경우가 많았습니다. 600-700회차 구간이었는데, 제 데이터상 이전 회차 번호가 다음 회차에 등장하는 경우가 많았던 것이죠. 744회차가 아직까지도 기억이 생생한데 공을 안 섞고 뽑았나라는 생각이 들기도 했습니다.. 이전 회차 번호를 그대로 가져다 쓰기만 해도 4900%의 이득을 볼 수 있었으니 이런 경우 때문에 특정 조합들은 1, 2, 3등의 당첨 금액이 많이 깎여나가는 게 아닐까 추측해 봅니다. 생일 조합 범주에 있는 번호처럼 사람들 생각은 다 똑같거든요. 마치 프로젝트 기획한다고 머리 쥐어짜 내서 겨우 만들어냈는데 이미 누군가 만들어놓은 서비스가 있는 경우와 유사하죠..


[이전 회차 영향 분석 - 2]

 

# 이전 회차와 1개 이상 일치하는 회차 수와 0개인 회차 수 비교
one_or_more_matching = df_next[df_next['same_numbers'] >= 1].shape[0]
zero_matching = df_next[df_next['same_numbers'] == 0].shape[0]

print(f"Number of rounds with 1 or more matching numbers: {one_or_more_matching}")
print(f"Number of rounds with 0 matching numbers: {zero_matching}")

# 시각화
plt.figure(figsize=(10, 6))
sns.barplot(x=['1 or more matching', '0 matching'], y=[one_or_more_matching, zero_matching])
plt.title('Comparison of Rounds with 1 or More Matching Numbers vs 0 Matching Numbers')
plt.xlabel('Matching Numbers')
plt.ylabel('Number of Rounds')
plt.show()

이전 회차와 1개 이상 일치하는 회차 수와 0개인 회차 수 비교

한 곳에 모아봤습니다. 이렇게 보니까 차이가 크긴 하죠? 그래도 이러한 데이터로는 1등을 위한 완벽한 조합의 수를 만들어 낼 수는 없습니다.

 

 

 


 

 

그래서 뭐 어쩌라는 거냐

 

 

 

import random

def generate_random_lotto_numbers():
    return sorted(random.sample(range(1, 46), 6))

# 자동으로 로또 번호 선택
random_lotto_numbers = generate_random_lotto_numbers()
print(f"Randomly selected lotto numbers: {random_lotto_numbers}")

 

본인의 신념대로 하십시오. 초지일관 번호 하나로 매 회차 5게임씩 지르던가, 자동으로 돌리던가. 위 코드처럼 본인이 만든 코드로 매주 조합을 바꿔서 선택하는 것도 나쁘진 않죠. 하지만 [자동]에 마킹 하나만 하면 되는데 굳이?

 

..아무튼 건전한 도박 생활합시다!

 

 

728x90

'Study > Python' 카테고리의 다른 글

python ExifTags와 argparse로 이미지 정보 출력  (0) 2019.08.16