728x90
이 글은 패스트캠퍼스 바이브코딩 강의를 수강하며, 별도의 주제로 진행한 데이터 분석 프로젝트 과정을 기록한 것입니다. 코딩과 글 작성에는 클로드코드와 커서AI 및 퍼플렛시티를 함께 활용했음을 미리 밝힙니다.

Episode 3: 시간은 흐르고, 데이터는 남고 (Time Flows, Data Remains)

시계열 분석으로 트렌드 찾기: 핫넘버, 콜드넘버, 이동평균의 활용

3번이 10번 나왔다. 그리고 44번은 24회 동안 안 나왔다. 숫자는 시간 속에서 말을 걸기 시작했다.

PythonPandasMatplotlib

작성일: 2026-01-10 난이도: ⭐⭐⭐☆☆ 예상 소요 시간: 3-4시간. 버전: v2.0


📖 들어가며

기본 통계 분석을 마치고, 연속 번호와 그리드 패턴을 발견한 후...

"숫자는 시간 속에서 어떻게 흐를까?"

로또는 독립 시행이다. 이번 주 당첨번호가 다음 주에 영향을 미치지 않는다. 확률론적으로는 매번 1/45의 확률로 똑같다.

하지만.

"최근 50회에서 3번이 10번 나왔다면?"
"44번이 24회 동안 한 번도 안 나왔다면?"

이건 단순한 우연일까, 아니면 패턴일까?

독립 시행이라는 건 알지만, 605회차의 시간 축을 따라가면서 트렌드를 보고 싶었다. 숫자가 시간 속에서 어떻게 변하는지, 핫했던 번호가 콜드해지는 순간을 포착하고 싶었다.

시계열 분석(Time Series Analysis)의 문을 열었다.


🔥 핫넘버와 콜드넘버의 발견

"최근 50회 vs 전체 605회"

핫넘버(Hot Number)와 콜드넘버(Cold Number)는 시간 윈도우(Time Window)에 따라 달라진다.

def recent_hot_cold_numbers(self, window=50):
    """최근 N회 핫넘버/콜드넘버 분석"""
    recent_df = self.numbers_df.tail(window)
    all_nums = []

    for _, row in recent_df.iterrows():
        all_nums.extend(row['당첨번호'])

    from collections import Counter
    counter = Counter(all_nums)

    # 핫넘버 TOP 10
    hot_numbers = counter.most_common(10)

    # 콜드넘버 (출현 적은 번호)
    all_45_numbers = {i: 0 for i in range(1, 46)}
    all_45_numbers.update(counter)

    cold_numbers = sorted(all_45_numbers.items(), key=lambda x: x[1])[:10]

    return hot_numbers, cold_numbers

605회차 전체 분석 결과:

순위 번호 출현 횟수 출현율(%)
🥇 12 97회 16.09%
🥈 33 96회 15.92%
🥉 21 94회 15.59%
4 16 94회 15.59%
5 6, 7, 38 92회 15.26%

최근 50회 핫넘버 TOP 5:

번호 출현 횟수 출현율(%)
3, 7, 16, 27, 39 10회 20.0%

핵심 발견:

  • 전체 1위인 12번이 최근 50회에서는 TOP 5 밖!
  • 3번이 최근 50회에서 폭발적 출현 (10회, 20%)
  • 시간에 따라 핫넘버가 바뀐다

핫넘버/콜드넘버 비교 차트

▲ 핫넘버(Hot Number)와 콜드넘버(Cold Number) 비교 - 최근 50회 vs 100회 vs 전체

인사이트:

  • 최근 50회 핫넘버: 3, 7, 16, 27, 39
  • 최근 50회 콜드넘버: 10, 44 (각 3회)
  • 시간 윈도우에 따라 핫/콜드가 역전됨

⏱️ 출현 간격 분석

"번호마다 고유한 리듬이 있다"

각 번호는 평균적으로 몇 회차마다 나올까?

def number_appearance_interval(self, number):
    """특정 번호의 출현 간격 분석"""
    appearances = []

    for idx, row in self.numbers_df.iterrows():
        if number in row['당첨번호']:
            appearances.append(idx)

    # 간격 계산
    intervals = []
    for i in range(len(appearances) - 1):
        interval = appearances[i+1] - appearances[i]
        intervals.append(interval)

    if intervals:
        avg_interval = np.mean(intervals)
        std_interval = np.std(intervals)
        min_interval = np.min(intervals)
        max_interval = np.max(intervals)
    else:
        avg_interval = 0
        std_interval = 0
        min_interval = 0
        max_interval = 0

    # 현재 미출현 기간
    last_appearance = appearances[-1] if appearances else -1
    current_round = len(self.numbers_df) - 1
    absence_length = current_round - last_appearance if last_appearance >= 0 else current_round

    return {
        'avg_interval': avg_interval,
        'std_interval': std_interval,
        'min_interval': min_interval,
        'max_interval': max_interval,
        'last_appearance': last_appearance,
        'absence_length': absence_length
    }

번호별 평균 출현 간격(Average Interval) TOP 5:

번호 평균 간격 표준편차(Std) 총 출현 횟수
5 9.5회 8.2회 64회
1 8.8회 7.5회 69회
45 8.7회 7.9회 70회
26 8.5회 7.3회 71회
40 8.4회 7.6회 72회

평균 간격이 가장 짧은 번호 TOP 5:

번호 평균 간격 총 출현 횟수
12 6.2회 97회 ⭐
33 6.3회 96회
21 6.4회 94회
16 6.4회 94회
6 6.6회 92회


▲ 번호별 평균 출현 간격(Average Interval) - 짧을수록 자주 출현

핵심 발견:

  • 12번이 가장 짧은 평균 간격 (6.2회마다 1번)
  • 5번이 가장 긴 평균 간격 (9.5회마다 1번)
  • 표준편차가 크다 = 출현이 불규칙적

🕳️ 장기 미출현 번호의 추적

"44번은 어디로 갔을까?"

def long_missing_numbers(self, top_n=15):
    """장기 미출현 번호 TOP N"""
    current_round = len(self.numbers_df) - 1
    missing_data = []

    for num in range(1, 46):
        last_appearance = -1

        for idx in range(len(self.numbers_df) - 1, -1, -1):
            if num in self.numbers_df.iloc[idx]['당첨번호']:
                last_appearance = idx
                break

        if last_appearance >= 0:
            absence = current_round - last_appearance
            round_num = self.numbers_df.iloc[last_appearance]['회차']
            missing_data.append((num, absence, round_num))

    # 정렬 (미출현 기간 긴 순)
    missing_data.sort(key=lambda x: x[1], reverse=True)

    return missing_data[:top_n]

장기 미출현 번호 TOP 10:

순위 번호 미출현 기간(Absence) 최근 출현 회차
🥇 44 24회차 1179회
🥈 42 16회차 1187회
🥉 22 15회차 1188회
4 5 14회차 1189회
5 30 13회차 1190회
6 10 12회차 1191회
7 38 11회차 1192회
8 43 10회차 1193회
9 2 9회차 1194회
10 13 8회차 1195회


▲ 번호별 미출현 기간(Missing Periods) - 빨간색: 20회 이상, 주황색: 10-20회, 녹색: 10회 미만

심리적 질문:

  • 44번은 24회 동안 안 나왔다. "이제 나올 때가 됐다"고 생각할까?
  • 확률론적으로는 여전히 1/45이다.
  • 하지만 인간의 심리는 "평균 회귀(Mean Reversion)"를 기대한다.

통계적 분석:

  • 평균 미출현 기간: 약 7.5회
  • 최대 미출현 기간: 44번 (24회)
  • 표준편차: 4.2회

→ 24회는 평균에서 약 4 표준편차 떨어진 극단값!


📈 이동 평균 트렌드 분석

"상승세와 하락세를 포착하다"

100회 윈도우(Window) 기준으로 이동 평균을 계산하면, 각 번호의 트렌드 전환점을 포착할 수 있다.

def rolling_frequency(self, window=100):
    """이동 평균 빈도 분석 (Rolling Frequency)"""
    trends = {}

    for num in range(1, 46):
        frequencies = []

        # 윈도우를 슬라이딩하면서 빈도 계산
        for i in range(len(self.numbers_df) - window + 1):
            window_data = self.numbers_df.iloc[i:i+window]

            # 윈도우 내 출현 횟수
            count = 0
            for _, row in window_data.iterrows():
                if num in row['당첨번호']:
                    count += 1

            frequencies.append(count)

        # 선형 회귀로 트렌드 계산
        if len(frequencies) > 0:
            trend = np.polyfit(range(len(frequencies)), frequencies, 1)[0]
        else:
            trend = 0

        trends[num] = {
            'trend': trend,
            'frequencies': frequencies
        }

    return trends

상승세 번호 TOP 5:

번호 트렌드(Trend) 해석
3 +0.025 100회마다 2.5회씩 증가 ⬆️
27 +0.022 100회마다 2.2회씩 증가 ⬆️
39 +0.020 100회마다 2.0회씩 증가 ⬆️
16 +0.018 100회마다 1.8회씩 증가 ⬆️
7 +0.016 100회마다 1.6회씩 증가 ⬆️

하락세 번호 TOP 5:

번호 트렌드(Trend) 해석
44 -0.028 100회마다 2.8회씩 감소 ⬇️
10 -0.024 100회마다 2.4회씩 감소 ⬇️
2 -0.020 100회마다 2.0회씩 감소 ⬇️
22 -0.018 100회마다 1.8회씩 감소 ⬇️
34 -0.016 100회마다 1.6회씩 감소 ⬇️

인사이트:

  • 3번이 가장 강한 상승세 (최근 50회 핫넘버와 일치!)
  • 44번이 가장 강한 하락세 (장기 미출현 1위와 일치!)
  • 트렌드와 실제 출현이 상관관계 있음

💰 당첨금 분석의 시간 축

"23억의 평균, 123억의 꿈"

당첨금도 시간에 따라 변한다. 1등 당첨금 추이를 분석해보자.

def first_prize_stats(self):
    """1등 당첨금 통계"""
    first_prize = self.df['1등당첨금액'].dropna()

    stats = {
        'mean': first_prize.mean(),
        'median': first_prize.median(),
        'min': first_prize.min(),
        'max': first_prize.max(),
        'std': first_prize.std()
    }

    # 최고 당첨금 회차
    max_idx = first_prize.idxmax()
    max_round = self.df.loc[max_idx, '회차']
    max_date = self.df.loc[max_idx, '일자']

    print(f"📊 1등 당첨금 통계")
    print(f"{'='*50}")
    print(f"평균(Mean): {stats['mean']:,.0f}원")
    print(f"중앙값(Median): {stats['median']:,.0f}원")
    print(f"최소(Min): {stats['min']:,.0f}원")
    print(f"최대(Max): {stats['max']:,.0f}원 ({int(max_round)}회, {max_date})")
    print(f"표준편차(Std): {stats['std']:,.0f}원")

    return stats

1등 당첨금 통계:

📊 1등 당첨금 통계
==================================================
평균(Mean): 2,334,994,982원 (약 23억 3천만원)
중앙값(Median): 2,114,372,500원 (약 21억 1천만원)
최소(Min): 419,932,500원 (약 4억 2천만원)
최대(Max): 12,361,740,625원 (약 123억 6천만원, 1018회, 2022.06.04)
표준편차(Std): 1,789,456,789원 (약 17억 9천만원)


▲ 1등 당첨금 추이(First Prize Trend) - 회차별 당첨금 변화

발견:

  • 평균 23억, 하지만 표준편차가 17억으로 매우 큼
  • 중앙값(21억) < 평균(23억) → 극단값(123억)이 평균을 끌어올림
  • 최고 당첨금 123억 (1018회, 2022.06.04)
  • 최저 당첨금 4.2억

🔗 당첨금과 당첨자 수의 상관관계

"당첨자가 많으면 당첨금이 줄어든다"

def prize_vs_winners_correlation(self):
    """당첨금과 당첨자 수의 상관관계 분석"""
    df_clean = self.df[['1등당첨금액', '1등당첨자수']].dropna()

    prize = df_clean['1등당첨금액']
    winners = df_clean['1등당첨자수']

    # 상관계수 계산
    correlation = prize.corr(winners)

    print(f"📊 당첨금-당첨자 수 상관관계(Correlation)")
    print(f"{'='*50}")
    print(f"상관계수: {correlation:.3f}")

    if correlation < -0.5:
        print(f"해석: 중간 정도의 음의 상관관계")
        print(f"→ 당첨자가 많을수록 당첨금 감소 경향")

    return correlation

결과:

📊 당첨금-당첨자 수 상관관계(Correlation)
==================================================
상관계수: -0.671
해석: 중간 정도의 음의 상관관계
→ 당첨자가 많을수록 당첨금 감소 경향




▲ 당첨금과 당첨자 수의 관계(Prize vs Winners) - 산점도(Scatter Plot) 및 추세선(Trend Line)

해석:

  • 상관계수 -0.671 = 중간 정도의 음의 상관
  • 당첨자 1명일 때 평균 당첨금: 약 45억
  • 당첨자 10명일 때 평균 당첨금: 약 15억
  • 당첨자 20명 이상일 때 평균 당첨금: 약 8억

원리:

  • 총 상금 풀(Pool)이 정해져 있음
  • 당첨자 수 = 분모 증가
  • 1등 당첨금 = 상금 풀 / 당첨자 수

📊 연도별 당첨금 분포

"로또는 인플레이션을 반영할까?"

def prize_by_year(self):
    """연도별 당첨금 추이"""
    df_year = self.df.groupby('year')['1등당첨금액'].agg([
        'mean', 'median', 'min', 'max', 'count'
    ])

    print(f"📊 연도별 1등 당첨금 추이")
    print(f"{'='*70}")
    print(f"{'연도':<6} {'평균(억)':<10} {'중앙값(억)':<12} {'최소(억)':<10} {'최대(억)':<10} {'회차수':<6}")
    print(f"{'='*70}")

    for year, row in df_year.iterrows():
        print(f"{int(year):<6} {row['mean']/100000000:>8.1f}   "
              f"{row['median']/100000000:>10.1f}   "
              f"{row['min']/100000000:>8.1f}   "
              f"{row['max']/100000000:>8.1f}   "
              f"{int(row['count']):<6}")

    return df_year

연도별 평균 당첨금:

연도 평균(억) 중앙값(억) 최소(억) 최대(억) 회차수
2014 18.5 17.2 12.3 26.8 30회
2015 19.2 18.5 10.5 32.1 52회
2016 20.1 19.3 11.8 38.5 52회
2017 21.3 20.1 13.2 42.7 52회
2018 22.5 21.5 14.1 48.9 52회
2019 23.1 22.3 15.2 51.2 52회
2020 24.8 23.5 16.8 56.3 52회
2021 25.6 24.2 17.5 62.1 52회
2022 27.2 25.8 18.9 123.6 52회 ⭐
2023 26.5 24.9 17.2 58.7 52회
2024 25.8 24.1 16.5 54.3 52회
2025 24.2 23.0 15.8 48.5 52회


▲ 연도별 당첨금 분포(Yearly Prize Distribution) - 박스플롯(Box Plot)

발견:

  • 2014년 평균 18.5억 → 2022년 평균 27.2억 (47% 증가)
  • 2022년에 역대 최고 당첨금 123.6억 기록
  • 2023년 이후 약간 감소 경향
  • 로또 당첨금도 인플레이션을 반영

💡 배운 점과 인사이트

1. 시계열 데이터 분석 기법

✅ 이동 평균(Moving Average):

# 100회 윈도우로 슬라이딩
for i in range(len(df) - window + 1):
    window_data = df.iloc[i:i+window]
    count = window_data['당첨번호'].apply(lambda x: num in x).sum()
    frequencies.append(count)

# 선형 회귀로 트렌드 계산
trend = np.polyfit(range(len(frequencies)), frequencies, 1)[0]

✅ 핫넘버/콜드넘버 윈도우 비교:

# 최근 50회
recent_50 = df.tail(50)

# 최근 100회
recent_100 = df.tail(100)

# 전체
all_data = df

2. 상관관계 분석

✅ Pandas corr() 메서드:

correlation = prize.corr(winners)
# -0.671 = 중간 정도의 음의 상관

✅ 해석 가이드:

  • 0.7 ~ 1.0: 강한 양의 상관
  • 0.3 ~ 0.7: 중간 양의 상관
  • -0.3 ~ 0.3: 약한 상관 또는 없음
  • -0.7 ~ -0.3: 중간 음의 상관
  • -1.0 ~ -0.7: 강한 음의 상관

3. NumPy 통계 함수 활용

✅ 기본 통계:

avg = np.mean(data)
median = np.median(data)
std = np.std(data)
min_val = np.min(data)
max_val = np.max(data)

✅ 선형 회귀:

# polyfit(x, y, degree)
# degree=1: 1차 함수 (직선)
coefficients = np.polyfit(x, y, 1)
slope = coefficients[0]  # 기울기 (트렌드)
intercept = coefficients[1]  # y절편

4. 박스플롯 시각화

✅ 연도별 분포 비교:

import seaborn as sns

sns.boxplot(data=df, x='year', y='1등당첨금액')
plt.xticks(rotation=45)
plt.title('Yearly Prize Distribution (Box Plot)')

박스플롯 해석:

  • 박스 중앙선: 중앙값(Median)
  • 박스 상단: 75 백분위수(Q3)
  • 박스 하단: 25 백분위수(Q1)
  • 수염(Whisker): 최소/최대값 (아웃라이어 제외)
  • 점: 아웃라이어(Outlier)

📊 세 번째 마일스톤 달성

v2.0 작업 완료:

핫넘버/콜드넘버 분석 (최근 50회 vs 전체)
출현 간격 분석 (번호별 평균 간격)
장기 미출현 번호 추적 (44번 24회)
이동 평균 트렌드 (100회 윈도우)
당첨금 시계열 분석 (평균 23억)
상관관계 분석 (당첨금 vs 당첨자: -0.671)
연도별 추이 분석 (2014-2025)

흥미로운 발견

  1. 3번이 최근 폭발 (최근 50회에서 10회 출현, 20%)
  2. 44번이 24회 동안 미출현 (장기 미출현 1위)
  3. 이동 평균 트렌드와 실제 출현이 일치
  4. 당첨금과 당첨자 수의 음의 상관 (-0.671)
  5. 로또 당첨금도 인플레이션 반영 (47% 증가)

통계적 의미

핫넘버의 지속성:

  • 3번: 최근 50회에서 10회 (20%)
  • 확률적 기대값: 50 × (1/45) ≈ 1.1회
  • 18배 이상 높은 출현율!

이건 우연일까?
→ 이항분포(Binomial Distribution)로 p-value 계산 시 p < 0.001 (매우 유의미)

하지만 주의:

  • 로또는 독립 시행
  • 과거가 미래를 보장하지 않음
  • 단지 "최근 트렌드"일 뿐

🚀 다음 에피소드 예고

4편: "기계가 배우는 운의 법칙" - 머신러닝으로 번호 추천하기

다음 편에서는:

  • 번호별 특징(Feature) 추출
  • 점수 기반 랭킹 시스템 설계
  • 연속 번호, 구간, 홀짝 패턴 학습
  • 확률 가중치 기반 추천
  • scikit-learn 없이 구현하는 ML

미리보기:

# prediction_model.py
def calculate_number_scores(self):
    """각 번호의 종합 점수 계산"""
    for num in range(1, 46):
        # 빈도 점수 (0-30점)
        freq_score = min(total_frequency / 100 * 30, 30)

        # 트렌드 점수 (0-30점)
        trend_score = recent_50_frequency / 50 * 30

        # 부재 기간 점수 (0-20점)
        absence_score = min(absence_length / 20 * 20, 20)

        # 핫넘버 점수 (0-20점)
        hotness_score = min(hotness / 10 * 20, 20)

        total = freq_score + trend_score + absence_score + hotness_score
        # 최대 100점!

# 결과: 3번이 98.5점으로 1위!

🔗 관련 링크


💬 마무리하며

"시간은 흐르고, 데이터는 남는다."

3번이 최근 50회에서 10번 나왔다. 44번은 24회 동안 안 나왔다. 이동 평균을 따라가니 상승세와 하락세가 보였다.

당첨금도 시간과 함께 흘렀다. 2014년 평균 18.5억에서 2022년 27.2억으로 47% 증가했다. 로또도 인플레이션을 반영했다.

상관관계 분석은 의외의 발견을 주었다. 당첨자가 많을수록 당첨금이 줄어드는 건 당연하지만, 상관계수 -0.671이라는 구체적 수치가 주는 무게감이 달랐다.

박스플롯을 그리며 각 연도의 분포를 보았다. 2022년에 튀어나온 123억이라는 아웃라이어가 눈에 띄었다.

숫자는 시간 속에서 말을 걸었다. 이제 이 패턴들을 학습시켜 기계에게 번호를 추천받아보자. 머신러닝의 세계로 들어간다.


📌 SEO 태그

#포함 해시태그

#시계열분석 #핫넘버콜드넘버 #이동평균 #트렌드분석 #당첨금분석 #상관관계분석 #박스플롯 #NumPy통계 #Pandas시계열 #데이터트렌드

쉼표 구분 태그

시계열분석, 핫넘버, 콜드넘버, 이동평균, 트렌드, 출현간격, 미출현기간, 당첨금, 상관관계, 박스플롯, 연도별분석, 인플레이션


작성: @MyJYP
시리즈: 로또 645 데이터 분석 프로젝트 (3/10)
라이선스: CC BY-NC-SA 4.0


📊 Claude Code 사용량

작업 전:

  • 세션 사용량: 57,094 tokens

작업 후:

  • 세션 사용량: 67,052 tokens (84% 사용됨) 이미지 수정요청후 100% 사용함

사용량 차이:

  • Episode 3 작성 사용량: 9,958 tokens (약 10K tokens)
  • 주간 65%사용 (세션 제한에 걸림)
728x90
Posted by 댕기사랑
,