-
파이썬 requests로 만든 로또 번호 예측코딩 2022. 6. 27. 23:26
혹시 로또를 자동으로 샀을 때 1등에 당첨될 확률이 얼마나 되는지 아는가?
약 0.000012%정도 된다.
포켓몬빵을 사서 나온 띠부띠부씰에서 피카츄가 3마리 연속으로 나올 확률(0.000025%)보다 2배나 낮다!
이렇게 보니까 띠부띠부씰이 더 대단해 보이는 느낌이 없잖아 있지만, 아무튼.
피카츄 3마리(0.000025%) (출처 : SK 텔레콤 뉴스룸) 이렇게 확률이 낮은 로또 번호를 통계로 내려보면 어떻게든 확률을 높일 수 있지 않을까? 라는 생각으로 이 프로젝트를 시작했다.
1. 뭘 통계로 내리는가?
통계를 내리기 이전, 어떤 요소를 통계로 내려야 할지 정해야한다.
동행복권 당첨통계 페이지 놀랍게도, 동행복권 홈페이지에서 로또 당첨 번호에 대한 통계를 대략적으로 제공한다.
그래서 동행복권에서 제공하는 번호별 통계, 색상 통계, 홀짝 통계, 미출현 번호를 통계로 내리고, 당첨 번호의 합계까지 계산해 보도록 하겠다.
2. 일단 코딩
코딩 환경: Jupyter Notebook
그래서 각각의 그래프 별로 Cell을 할당했다.
import requests import json import matplotlib.pyplot as plt import random def GetLotto(start=1, end=9999): url = "https://www.dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=" jsonstr = ["drwtNo1", "drwtNo2", "drwtNo3", "drwtNo4", "drwtNo5", "drwtNo6", "bnusNo"] LottoNumber = dict() for i in range(start, end): req = requests.get(url+str(i)) Lottojson = req.json() if Lottojson["returnValue"] == "fail": break LottoNumber[i] = [Lottojson[str] for str in jsonstr] return LottoNumber def OddEven(NumList): return {"odd":[oddNum for oddNum in NumList if oddNum%2==1], "even":[evenNum for evenNum in NumList if evenNum%2==0]} def Total(NumList): return sum(NumList) def Color(NumListList): returnDict = {i:0 for i in range(1,45,10)} for NumList in NumListList: for Num in NumList[0:6]: returnDict[10*((Num-1)//10)+1] += 1 return returnDict def Number(NumListList): returnDict = {i:0 for i in range(1,46)} for NumList in NumListList: for Num in NumList[0:6]: returnDict[Num] += 1 return returnDict def NoBall(NumListList): returnList = [i for i in range(0,46)] for NumList in NumListList: for Num in NumList[0:6]: returnList[Num] = 0 return [i for i in returnList if i != 0] LottoNumber = GetLotto()
동행복권의 API가 그렇게까지 친절한 편은 아니라서, 가장 마지막 게임 회차를 수동으로 찾아야 한다.
만약, API에서 제공되는 json의 returnValue가 fail이라면, 게임 정보를 찾는 For문을 종료하는 식으로 회차 정보를 받았다.
https://www.dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=(회차번호) 로 API를 사용한다.
2.1 색상 통계
LottoColor = Color([LottoNumber[i][0:6] for i in range(1,len(LottoNumber)+1)]) print(LottoColor) ratio = LottoColor.values() labels = ['1~10', '11~20', '21~30', '31~40', '41~45'] color = ['#fbc400', '#69c8f2', '#ff7272', '#aaa', '#b0d840'] plt.pie(ratio, labels=labels, autopct='%.2f%%', counterclock=False, colors=color) plt.show()
통계를 확인하기 위해 matlablib를 사용했다.
로또6/45 1~1021회차 색상통계 생각보다 평균적이다.
1~10, 11~20, 21~30, 31~40의 확률 기댓값은 22.2% 41~45의 확률 기대값은 11.1%.
기댓값에서 ±1% 정도 벗어난 셈이다.
2.2 당첨번호의 합계
LottoNumberTotalList = [Total(LottoNumber[i][0:6]) for i in range(1,len(LottoNumber)+1)] plt.plot([i for i in range(1,277)], [LottoNumberTotalList.count(i) for i in range(1, 277)]) plt.xlabel('Number Total') plt.ylabel('Frequency') plt.show()
로또6/45 1~1021회차 당첨번호 합계 통계 역시나. 대체적으로 정규분포 곡선을 따르고 있다.
당첨번호의 최솟값은 21(1+2+3+4+5+6) 최댓값은 255(40+41+42+43+44+45) 평균값은 138.
그래서 대략 138을 평균값으로 가진 정규분포 곡선이 나온 거 같다.
2.3 당첨번호별 통계
LottoNumberDict = Number([LottoNumber[i][0:6] for i in range(1,len(LottoNumber)+1)]) SortedDictList = sorted(LottoNumberDict.items(), key=lambda item: item[1], reverse=True) print(SortedDictList[0:6]) print(SortedDictList[-7:-1]) plt.bar(range(1,46), [LottoNumberDict[i] for i in range(1,46)]) plt.xlabel("Number") plt.ylabel("Frequency") plt.show()
로또6/45 1~1021회차 당첨번호별 통계 흠. 뭐 그냥저냥이다.
추가적으로, 가장 많이 나온 번호와 가장 적게 나온 번호도 통계를 내렸는데,
1위 2위 3위 4위 5위 6위 34 (158회) 18 (151회) 27 (148회) 12 (147회) 17 (147회) 13 (146회) 40위 41위 42위 43위 44위 45위 6 (126회) 28 (125회) 23 (124회) 30 (123회) 22 (116회) 32 (114회) 1위와 45위의 차이가 44회 차이. 34번이 32번보다 약 38% 정도 더 많이 당첨번호로 나온 것이다.
2.4 홀짝 통계
LottoOddList = [len(OddEven(LottoNumber[i][0:6])['odd']) for i in range(1, len(LottoNumber)+1)] plt.bar(range(0,7), [LottoOddList.count(i) for i in range(7)]) plt.xlabel("Odd Number Amount") plt.ylabel("Frequency") plt.show()
로또6/45 1~1021회차 홀짝 통계 이것 또한 정규분포 곡선을 따른다.
약간 주목할만한 점이 홀수가 4번 나오는 경우가 2번 나오는 경우보다 많은데, 이는 실제로 로또 번호에 홀수가 더 많기 때문으로 추정된다.
2.5 미출현 번호
NoBallDict = dict() for i in range(len(LottoNumber),0,-1): NoBallTemp = NoBall([LottoNumber[j][0:6] for j in range(i, len(LottoNumber))]) NoBallDict[i] = NoBallTemp if NoBallTemp == []: break plt.figure(figsize=(10,10)) plt.xticks([i for i in NoBallDict.keys()]) plt.yticks(range(1,46)) plt.xlabel("Lottery Round") plt.ylabel("Number that does not appear") plt.scatter(sum([[i]*len(j) for i, j in NoBallDict.items()], []), sum(NoBallDict.values(), [])) plt.show()
로또6/45 1~1021회차 미출현 번호 대략 20회 차가 되면 모든 번호가 나오는 거 같다.
아주 공교롭게도, 1001회 차에서부터 1021회 차까지 나오지 않은 번호 중 하나인 32번은, 2.3 당첨번호별 통계에서 알아본, 가장 적게 나온 번호이다! 재밌군..
3. 그래서 어떻게 예측하는가?
대략 5가지의 통계 결과를 통해 여러 가지 전략을 짜 볼 수 있다!
3.1 색상 통계 확률에 따른 무작위
ColorRandomPer = sorted(random.sample(sum([[i]*j for i,j in LottoColor.items()], []), 6)) ColorRandom = [] for i in set(ColorRandomPer): ColorRandom += sorted(random.sample(range(i,(i+10 if i!=41 else 46)),ColorRandomPer.count(i))) print(sorted(ColorRandom))
2.1 색상 통계에서 나타난 확률에 따른 무작위이다.
여담이지만, 2.5 미출현 번호에서도 나온 테크닉이긴 한데, 좀 유용한 거 같아서 한 가지 테크닉을 소개하고자 한다.
더보기sum([[i]*j for i,j in Dict.items()], [])
Dict의 Value가 int형이면 사용할 수 있는 테크닉이다. 딕셔너리의 Key를 Value의 값만큼 List에 넣는다!
예시를 들어, Dict = {"Apple":3, "Banana":5, "Coconut":1} 라고 가정하면, 위의 코드의 결과는 다음과 같다.
['Apple', 'Apple', 'Apple', 'Banana', 'Banana', 'Banana', 'Banana', 'Banana', 'Coconut']
뭔가 이런 게 필요할 때가 있으면 활용하면 되겠다!
3.2 당첨번호의 합계에 따른 무작위
TotalRandomNum = random.choice(LottoNumberTotalList) print(TotalRandomNum) while True: TotalRandom = [] TempComp = [item for item in range(1,46)] while len(TotalRandom) != 5: TotalRandom.append(random.choice(TempComp)) TempComp = [item for item in range(1,46) if item not in TotalRandom] if sum(TotalRandom)+sum(TempComp[0:6-len(TotalRandom)])>TotalRandomNum or sum(TotalRandom)+sum(TempComp[-7+len(TotalRandom):-1])<TotalRandomNum: TotalRandom.pop() if TotalRandomNum-sum(TotalRandom) not in TotalRandom: TotalRandom.append(TotalRandomNum-sum(TotalRandom)) break print(sorted(TotalRandom))
생각보다 복잡했다. 일단 합계 선택은 그동안 나온 번호 총합 중에서 랜덤으로 뽑는 것으로, 물론 평균값인 138 근처 값들이 더 높은 확률로 나타난다.
그리고 정해진 합계에서 랜덤으로 값을 추출하는 경우는 다음과 같은 알고리즘을 사용했다.
1) TotalRandom에 없는 값 중 랜덤으로 하나를 뽑는다.
2) TotalRandom의 예상 최솟값(남은 자리에 1, 2, 3... 을 채움)과 예상 최댓값(남은 자리에 45, 44, 43...을 채움) 계산
3) 예상 최솟값 <= 목표 합계 <= 예상 최댓값 인지 판별한다.
3-1) 합계가 최솟값과 최댓값 사이에 있으면, 과정 1)을 반복한다.
3-2) 합계가 최솟값과 최댓값 사이에 없으면, 과정 1)에서 넣은 숫자를 제거하고, 과정 1)을 반복한다.
4) TotalRandom의 값이 5개가 된다면, 1) ~ 3) 과정을 정지한다.
5) (목표 합계 - TotalRandom의 합계) 값이 중복되는지 판별한다.
5-1) 중복된다면, 처음부터 다시 한다.
5-2) 중복되지 않는다면, 해당 값이 최종 값이다.주절주절 쓰니까 뭔가 이해 안 가서 그냥 해당 알고리즘의 출처를 남기겠다.
https://blog.naver.com/askmrkwon/220804058585
3.3 당첨번호별 확률에 따른 무작위
NumberPer = sum([[i]*j for i,j in LottoNumberDict.items()], []) NumberRandom = [] while len(NumberRandom) != 6: NumberRandom.append(random.choice(NumberPer)) if NumberRandom[-1] in NumberRandom[0:-1]: NumberRandom.pop() print(sorted(NumberRandom))
2.3 당첨번호별 통계로 구한 확률에 기반하여 랜덤하게 구했다.
3.4 홀짝 확률에 따른 무작위
OddPer = random.choice(LottoOddList) OddRandom = [i for i in random.sample(range(1,46,2), OddPer)] + [i for i in random.sample(range(2,45,2), 6-OddPer)] print(sorted(OddRandom))
홀짝 확률에 따른 무작위다.
3.5 미출현 번호에 따른 무작위
for i in range(len(LottoNumber), 0, -1): NoBallRandomPool = NoBall([LottoNumber[j][0:6] for j in range(i, len(LottoNumber))]) if len(NoBallRandomPool) < 6: NoBallRandomPool = NoBall([LottoNumber[j][0:6] for j in range(i+1, len(LottoNumber))]) break NoBallRandomPool = random.sample(NoBallRandomPool, 6) print(sorted(NoBallRandomPool))
미출현 번호에 따른 무작위다. 우선 미출현 번호가 6개 이하가 되기 전 회차의 번호를 통해 최대한 번호의 수를 추리고,
6개를 무작위로 뽑는 형태다. 사실상 미출현 번호가 6개라면 무작위는 아니다!
3.6 종합 통계에 따른 무작위
OddPer = random.choice(LottoOddList) OddEvenList = [1]*OddPer+[0]*(6-OddPer) NoBallRandomPool = NoBall([LottoNumber[j][0:6] for j in range(len(LottoNumber)-5, len(LottoNumber))]) random.shuffle(OddEvenList) while True: TotalRandomNum = random.choice(LottoNumberTotalList) if TotalRandomNum%2 == OddPer%2: break while True: TotalRandom = [] TempComp = [item for item in NoBallRandomPool] while len(TotalRandom) != 5: TotalRandom.append(random.choice([i for i in TempComp if i%2==OddEvenList[len(TotalRandom)]])) TempComp = [item for item in NoBallRandomPool if item not in TotalRandom] if sum(TotalRandom)+sum(TempComp[0:6-len(TotalRandom)])>TotalRandomNum or sum(TotalRandom)+sum(TempComp[-7+len(TotalRandom):-1])<TotalRandomNum: TotalRandom.pop() if TotalRandomNum-sum(TotalRandom) not in TotalRandom and (TotalRandomNum-sum(TotalRandom))%2==OddEvenList[-1]: TotalRandom.append(TotalRandomNum-sum(TotalRandom)) break print(sorted(TotalRandom))
3.2 당첨번호의 합계에 따른 무작위, 3.4 홀짝 확률에 따른 무작위, 3.5 미출현 번호에 따른 무작위 전략을 모두 합쳤다!
하지만, 이렇게 조건이 늘어나면 늘어날수록, 계산에 실패할 확률 또한 늘어났다! 이는 나중에 개선해야 할 점이다.
3.7 그냥 무작위
print(sorted(random.sample(range(1,46), 6)))
사실 이 전략은 위의 통계 결과와는 전혀 관계는 없지만, 아무튼 특정 숫자를 고른 거다!
4. 결과?
3번에서 기술한 전략들을 기반으로, 당첨 번호를 구해보았다! 나온 당첨 예상 번호는 다음과 같다.
(기준 : 1 ~ 1021회차 통계 기반)
전략 1번 번호 2번 번호 3번 번호 4번 번호 5번 번호 6번 번호 3.1 색상 통계 1 7 9 17 19 40 3.2 당첨번호의 합계 18 26 31 34 39 40 3.3 당첨번호별 확률 5 6 20 30 39 45 3.4 홀짝 확률 1 3 24 35 37 44 3.5 미출현 번호 2 6 7 10 32 43 3.6.1 종합 통계 7 8 11 20 24 40 3.6.2 종합 통계 8 14 29 35 36 43 3.6.3 종합 통계 8 24 32 35 43 44 3.6.4 종합 통계 7 16 21 24 35 44 3.7 무작위 5 6 12 28 32 45 오늘(2022-06-27) 기준으로 구매 가능한 1022회 차 로또를 직접 구매하여, 후기를 남기고자 한다.
일단 결과가 어떻든 간에 당부할 사항이 하나 있다.
로또는 언제까지나 독립 시행의 결과이며, 해당 분석에 따라 로또를 구입하여 손해를 본다 하더라도, 나의 책임이 없음을 알린다.
LottoSimluatorGraph.ipynb0.09MB해당 Jupyter Notebook 파일을 통해 1021회 차 이후 회차 통계 분석이나, 위의 전략을 통한 로또 당첨 예상 번호 생성을 언제나 할 수 있다!
'코딩' 카테고리의 다른 글
진짜 마지막 로또 이야기 (0) 2022.07.10 로또 예측, 그 결과와 이후 (0) 2022.07.02 파이썬 List로 만든 숫자 한글 변환기 (0) 2022.06.14 파이썬 json으로 만든 인스타그램 DM txt파일로 내보내기 (0) 2022.06.02 파이썬 random으로 만든 로또 시뮬레이터 (0) 2021.05.16