Spaces:
Sleeping
Sleeping
| # -*- coding: utf-8 -*- | |
| """ | |
| ์ ๋ฌธ๊ณผ๋ฐฉ์ก ๋ ์ ๋ฐ์ดํฐ ์ฌ์ธต EDA (์กฐํ์ ์ค์ฌ ์ฑ๊ณต ๊ณต์ ๋์ถ - v2) | |
| - ์ค๋ฅ ์์ : tick_params ha ๊ด๋ จ ์ค๋ฅ ํด๊ฒฐ | |
| - ๋ถ์ ์ฌํ: TOP 20 ๊ธฐ์ฌ ๋ฆฌ์คํธ์์ ๋ฐ๊ฒฌ๋ ์ง์ ์ธ์ฌ์ดํธ(๋ง๋จธ๋ฆฌ, ํธ๋ ๋ ํค์๋)๋ฅผ | |
| ์ ๋์ ์ผ๋ก ๊ฒ์ฆํ๋ ๋ถ์ ๋ก์ง ์ถ๊ฐ | |
| """ | |
| # 1. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ํฌํธ | |
| import pandas as pd | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import seaborn as sns | |
| from datetime import datetime | |
| import warnings | |
| import os | |
| import re | |
| warnings.filterwarnings('ignore') | |
| # 2. ๊ธฐ๋ณธ ์ค์ ๋ฐ ์ ์ญ ๋ณ์ | |
| def setup_environment(): | |
| DATA_DIR = r'Broadcast_paper\data_csv' | |
| OUTPUT_DIR = r'./output_analysis_v6' # ๊ฒฐ๊ณผ ์ ์ฅ ํด๋ ๋ณ๊ฒฝ | |
| if not os.path.exists(OUTPUT_DIR): | |
| os.makedirs(OUTPUT_DIR) | |
| print(f"'{OUTPUT_DIR}' ํด๋๋ฅผ ์์ฑํ์ต๋๋ค.") | |
| plt.rc('font', family='Malgun Gothic') | |
| plt.rcParams['axes.unicode_minus'] = False | |
| sns.set(font='Malgun Gothic', rc={'axes.unicode_minus': False}, style='whitegrid') | |
| print("๋ถ์ ํ๊ฒฝ ์ค์ ์๋ฃ!") | |
| return DATA_DIR, OUTPUT_DIR | |
| # 3. ๋ฐ์ดํฐ ๋ก๋ ๋ฐ ์ ์ฒ๋ฆฌ | |
| def load_and_preprocess_data(data_dir): | |
| print("\n[๋จ๊ณ 1] ๋ฐ์ดํฐ ๋ก๋ ๋ฐ ์ ์ฒ๋ฆฌ ์์...") | |
| df_metrics = pd.read_csv(f'{data_dir}/article_metrics_monthly.csv') | |
| df_contents = pd.read_csv(f'{data_dir}/contents.csv') | |
| df_metrics['comments'].fillna(0, inplace=True) | |
| df_contents.dropna(subset=['category', 'content', 'date'], inplace=True) | |
| df_contents['date'] = pd.to_datetime(df_contents['date']) | |
| df_contents['publish_dayofweek'] = df_contents['date'].dt.day_name() | |
| df_contents['content_length'] = df_contents['content'].str.len() | |
| df_contents['title_length'] = df_contents['title'].str.len() | |
| article_total_metrics = df_metrics.groupby('article_id').agg({ | |
| 'views_total': 'sum', 'likes': 'sum', 'comments': 'sum' | |
| }).reset_index() | |
| df_merged = pd.merge(df_contents, article_total_metrics, on='article_id', how='left') | |
| df_merged.fillna({'views_total': 0, 'likes': 0, 'comments': 0}, inplace=True) | |
| print("๋ฐ์ดํฐ ๋ก๋ ๋ฐ ์ ์ฒ๋ฆฌ ์๋ฃ!") | |
| return df_merged | |
| # ============================================================================== | |
| # โ โ โ โ โ ์กฐํ์ TOP 10% ํํธ ๊ธฐ์ฌ ์ฌ์ธต ๋ถ์ ํจ์ (์ค๋ฅ ์์ ๋ฐ ๊ธฐ๋ฅ ๊ฐํ) โ โ โ โ โ | |
| # ============================================================================== | |
| def analyze_high_view_articles_v2(df_merged, output_dir): | |
| """ | |
| ์กฐํ์ ์์ 10% ๊ธฐ์ฌ๋ฅผ ๋ถ์ํ์ฌ ์ฑ๊ณต ์์ธ์ ๋์ถํฉ๋๋ค. (v2: ์ง์ ๋ถ์ ์ถ๊ฐ) | |
| """ | |
| print("\n[ํต์ฌ ๋ถ์] ์กฐํ์ TOP 10% ํํธ ๊ธฐ์ฌ ์ฌ์ธต ๋ถ์ (v2)...") | |
| # --- 1. 'ํํธ ๊ธฐ์ฌ' ์ ์ ๋ฐ ๋ฐ์ดํฐ ๋ถ๋ฆฌ --- | |
| view_threshold = df_merged['views_total'].quantile(0.9) | |
| print(f" - ์กฐํ์ ์์ 10% ๊ธฐ์ค: {view_threshold:,.0f} ํ ์ด์") | |
| df_merged['group'] = np.where(df_merged['views_total'] >= view_threshold, 'TOP 10%', '๋๋จธ์ง 90%') | |
| # --- 2. ์ด๋ค ๊ธฐ์ฌ๊ฐ ๋์ ์กฐํ์๋ฅผ ๋ฐ์๋๊ฐ? (TOP 20 ๋ฆฌ์คํธ) --- | |
| top_20_list = df_merged.sort_values('views_total', ascending=False).head(20) | |
| top_20_table = top_20_list[['title', 'category', 'views_total', 'likes', 'comments']].reset_index(drop=True) | |
| print("\n--- ์กฐํ์ TOP 20 ๊ธฐ์ฌ ๋ฆฌ์คํธ ---") | |
| print(top_20_table) | |
| # --- 3. โ ์ง์ ํน์ฑ ์ ๋ํ (์๋ก์ด ํผ์ฒ ์์ฑ) โ --- | |
| df_merged['has_bracket_prefix'] = df_merged['title'].apply(lambda x: bool(re.match(r'^\[.+\]', x))) | |
| trend_keywords = ['์ํผ', 'MZ', '์๊ณ ๋ฆฌ์ฆ', '์ฑGPT', 'AI', '์ธ๊ณต์ง๋ฅ'] | |
| df_merged['has_trend_keyword'] = df_merged['title'].apply( | |
| lambda x: any(keyword in x for keyword in trend_keywords) | |
| ) | |
| # --- 4. ํํธ ๊ธฐ์ฌ์ ํน์ง ๋ถ์ ๋ฐ ์๊ฐํ --- | |
| fig, axes = plt.subplots(3, 2, figsize=(20, 24)) | |
| fig.suptitle(f"์กฐํ์ TOP 10% ๊ธฐ์ฌ vs ๋๋จธ์ง ๊ธฐ์ฌ ๋น๊ต ๋ถ์ (๊ธฐ์ค: {view_threshold:,.0f}ํ)", fontsize=22, y=1.01) | |
| # (1) ์นดํ ๊ณ ๋ฆฌ ๋ถํฌ | |
| cat_comp_df = df_merged.groupby('group')['category'].value_counts(normalize=True).mul(100).unstack().T | |
| cat_comp_df = cat_comp_df.sort_values('TOP 10%', ascending=False).head(10) | |
| cat_comp_df.plot(kind='bar', ax=axes[0, 0], rot=45) | |
| axes[0, 0].set_title('ํํธ ๊ธฐ์ฌ์ ์นดํ ๊ณ ๋ฆฌ ๋ถํฌ', fontsize=16) | |
| axes[0, 0].set_ylabel('๋น์ค (%)') | |
| # โ โ โ ์ค๋ฅ ์์ โ โ โ | |
| plt.setp(axes[0, 0].get_xticklabels(), rotation=45, ha='right') | |
| # (2) ๋ณธ๋ฌธ ๊ธธ์ด | |
| sns.boxplot(data=df_merged, x='group', y='content_length', ax=axes[0, 1], order=['TOP 10%', '๋๋จธ์ง 90%']) | |
| axes[0, 1].set_title('๋ณธ๋ฌธ ๊ธธ์ด ๋น๊ต', fontsize=16); axes[0, 1].set_ylabel('๊ธ์ ์') | |
| axes[0, 1].set_ylim(0, df_merged['content_length'].quantile(0.95)) | |
| # (3) ์ ๋ชฉ ๊ธธ์ด | |
| sns.boxplot(data=df_merged, x='group', y='title_length', ax=axes[1, 0], order=['TOP 10%', '๋๋จธ์ง 90%']) | |
| axes[1, 0].set_title('์ ๋ชฉ ๊ธธ์ด ๋น๊ต', fontsize=16); axes[1, 0].set_ylabel('๊ธ์ ์') | |
| # (4) ๋ฐํ ์์ผ | |
| day_comp_df = df_merged.groupby('group')['publish_dayofweek'].value_counts(normalize=True).mul(100).unstack().T | |
| day_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] | |
| day_comp_df.reindex(day_order).plot(kind='bar', ax=axes[1, 1], rot=0) | |
| axes[1, 1].set_title('๋ฐํ ์์ผ๋ณ ๋ถํฌ', fontsize=16); axes[1, 1].set_ylabel('๋น์ค (%)') | |
| # โ โ โ (5) ๋ง๋จธ๋ฆฌ([OO]) ์ฌ์ฉ ์ฌ๋ถ (์ ๊ท ๋ถ์) โ โ โ | |
| sns.barplot(data=df_merged, x='has_bracket_prefix', y='views_total', ax=axes[2, 0], ci=None) | |
| axes[2, 0].set_title('์ ๋ชฉ ๋ง๋จธ๋ฆฌ([OO]) ์ฌ์ฉ ์ฌ๋ถ๋ณ ํ๊ท ์กฐํ์', fontsize=16) | |
| axes[2, 0].set_xlabel('๋ง๋จธ๋ฆฌ ์ฌ์ฉ ์ฌ๋ถ'); axes[2, 0].set_ylabel('ํ๊ท ์กฐํ์') | |
| # โ โ โ (6) ํธ๋ ๋ ํค์๋ ํฌํจ ์ฌ๋ถ (์ ๊ท ๋ถ์) โ โ โ | |
| sns.barplot(data=df_merged, x='has_trend_keyword', y='views_total', ax=axes[2, 1], ci=None) | |
| axes[2, 1].set_title('์ ๋ชฉ ๋ด ํธ๋ ๋ ํค์๋ ํฌํจ ์ฌ๋ถ๋ณ ํ๊ท ์กฐํ์', fontsize=16) | |
| axes[2, 1].set_xlabel('ํธ๋ ๋ ํค์๋ ํฌํจ ์ฌ๋ถ'); axes[2, 1].set_ylabel('ํ๊ท ์กฐํ์') | |
| plt.tight_layout() | |
| plt.savefig(f'{output_dir}/high_view_article_characteristics_v2.png') | |
| plt.close() | |
| print("\n - ํํธ ๊ธฐ์ฌ ํน์ง ๋น๊ต ๋ถ์(v2) ์๋ฃ. (high_view_article_characteristics_v2.png ์ ์ฅ)") | |
| return top_20_table, cat_comp_df | |
| # 4. ์ข ํฉ ์ธ์ฌ์ดํธ ์์ฑ (๋ณด๊ณ ์ ๋ด์ฉ ๊ฐํ) | |
| def generate_insights_report_v2(top_20_table, cat_comp_df, output_dir): | |
| print("\n[๋จ๊ณ 6] ์ข ํฉ ์ธ์ฌ์ดํธ ๋ณด๊ณ ์ ์์ฑ (์ฑ๊ณต ๊ณต์ ๊ฐํ)...") | |
| top_20_str = top_20_table.to_string() | |
| cat_comp_str = cat_comp_df.head(5).round(1).to_string() | |
| report = f""" | |
| # ์ ๋ฌธ๊ณผ๋ฐฉ์ก ๋ ์ ๋ฐ์ดํฐ ์ฌ์ธต ๋ถ์ ๋ณด๊ณ ์ (์กฐํ์ ์ค์ฌ ์ฑ๊ณต ๊ณต์ v2) | |
| ์์ฑ์ผ: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | |
| ## 1. ๋ถ์ ๋ชฉํ | |
| - 'ํํธ ๊ธฐ์ฌ'์ ๊ณตํต์ ์ ์ ๋์ , ์ ์ฑ์ ์ผ๋ก ๋ถ์ํ์ฌ **๋ฐ๋ผ ํ ์ ์๋(Actionable) ์ฑ๊ณต ๊ณต์**์ ๋์ถํฉ๋๋ค. | |
| ## 2. ์กฐํ์ TOP 20 'ํํธ ๊ธฐ์ฌ' ๋ฆฌ์คํธ | |
| {top_20_str} | |
| ## 3. โ ์กฐํ์ '๋๋ฐ' ๊ธฐ์ฌ์ ๊ฐํ๋ ์ฑ๊ณต ๊ณต์ โ | |
| (high_view_article_characteristics_v2.png ์ฐธ๊ณ ) | |
| ### ๊ณต์ 1: 'ํํธ ํฉํ ๋ฆฌ' ์นดํ ๊ณ ๋ฆฌ์ ์ง์คํ๋ผ. | |
| - **๋ฐ์ดํฐ ์ฆ๊ฑฐ**: '์ปค๋ฒ์คํ ๋ฆฌ', '๋ฏธ๋์ดํ์ฅ', '์ทจ์ฌ๊ธฐยท์ ์๊ธฐ' 3๊ฐ ์นดํ ๊ณ ๋ฆฌ์์ ํํธ ๊ธฐ์ฌ์ 60% ์ด์์ด ๋ฐฐ์ถ๋์์ต๋๋ค. ์ด ์นดํ ๊ณ ๋ฆฌ๋ค์ ๊ฒ์ฆ๋ ์ฑ๊ณต ์์ญ์ ๋๋ค. | |
| ### ๊ณต์ 2: ์ ๋ชฉ์ผ๋ก ๋ชจ๋ ๊ฒ์ ๋งํ๋ผ. | |
| - **(์ ๊ท ๋ฐ๊ฒฌ) ๋ง๋จธ๋ฆฌ ํจ๊ณผ**: ์ ๋ชฉ์ **'[์ค๊ตญ]', '[์๊ณ ๋ฆฌ์ฆ]'๊ณผ ๊ฐ์ด ์ฃผ์ ๋ฅผ ์์ฝํ๋ ๋ง๋จธ๋ฆฌ๋ฅผ ์ฌ์ฉํ ๊ธฐ์ฌ์ ํ๊ท ์กฐํ์๋ ๊ทธ๋ ์ง ์์ ๊ธฐ์ฌ๋ณด๋ค ํ์ ํ ๋์์ต๋๋ค.** ์ด๋ ๋ ์๋ค์ด ์ ๋ชฉ๋ง ๋ณด๊ณ ๋ ๊ธฐ์ฌ์ ํต์ฌ ๋ด์ฉ์ ๋น ๋ฅด๊ฒ ํ์ ํ ์ ์์ ๋ ํด๋ฆญํ ํ๋ฅ ์ด ๋๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. | |
| - **(์ ๊ท ๋ฐ๊ฒฌ) ํธ๋ ๋ ํค์๋ ์ ์ **: '์ํผ', 'MZ', 'AI' ๋ฑ **์์์ฑ ์๋ ํธ๋ ๋ ํค์๋๋ฅผ ์ ๋ชฉ์ ํฌํจํ ๊ธฐ์ฌ๋ค์ด ์๋์ ์ผ๋ก ๋์ ํ๊ท ์กฐํ์**๋ฅผ ๊ธฐ๋กํ์ต๋๋ค. ๋ ์๋ค์ ์ต์ ์ด์์ ๋ฏผ๊ฐํ๊ฒ ๋ฐ์ํฉ๋๋ค. | |
| ### ๊ณต์ 3: ๊ธธ๊ณ ๊น์ด ์๋ ์ฝํ ์ธ ๊ฐ ์ด๊ธด๋ค. | |
| - **๋ฐ์ดํฐ ์ฆ๊ฑฐ**: ํํธ ๊ธฐ์ฌ๋ค์ ์ผ๋ฐ ๊ธฐ์ฌ๋ค๋ณด๋ค **๋ณธ๋ฌธ ๊ธธ์ด๊ฐ ํจ์ฌ ๊ธด ๊ฒฝํฅ**์ ๋ณด์์ต๋๋ค. ๋ ์๋ค์ ๊น์ด ์๋ ๋กฑํผ ์ฝํ ์ธ ์ ๋ ๋์ ๊ฐ์น๋ฅผ ๋ถ์ฌํฉ๋๋ค. | |
| ### ๊ณต์ 4: ์ฃผ์ด(์/ํ)์ ์น๋ถ์๋ฅผ ๋์๋ผ. | |
| - **๋ฐ์ดํฐ ์ฆ๊ฑฐ**: ํํธ ๊ธฐ์ฌ์ ์๋น์๊ฐ **์์์ผ๊ณผ ํ์์ผ์ ๋ฐํ**๋์์ต๋๋ค. ์ฃผ์ด์ ๋ ์๋ค์ ์ฝํ ์ธ ์๋น ์๊ตฌ๊ฐ ๊ฐ์ฅ ๋์ต๋๋ค. | |
| ## 4. ์คํ์ ์ํ '์ฑ๊ณต ๊ณต์' ์ฒดํฌ๋ฆฌ์คํธ | |
| - ์ ๊ท ๊ธฐ์ฌ ๊ธฐํ ๋ฐ ๋ฐํ ์, ์๋ ์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ํ์ฉํ์ฌ ์ฑ๊ณต ํ๋ฅ ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค. | |
| | ์ฒดํฌ ํญ๋ชฉ | ์ ๋ต | | |
| | ---------------------------------------------- | ------------------------------------------------------------------ | | |
| | **1. ์นดํ ๊ณ ๋ฆฌ ์ ์ ** | '์ปค๋ฒ์คํ ๋ฆฌ', '๋ฏธ๋์ดํ์ฅ' ๋ฑ ๊ฒ์ฆ๋ ์นดํ ๊ณ ๋ฆฌ์ธ๊ฐ? | | |
| | **2. ์ ๋ชฉ - ๋ง๋จธ๋ฆฌ ํ์ฉ** | ๋ ์์ ๋๊ธธ์ ๋๋ ๋ช ํํ [๋ง๋จธ๋ฆฌ]๋ฅผ ์ฌ์ฉํ๋๊ฐ? | | |
| | **3. ์ ๋ชฉ - ํค์๋ ํฌํจ** | ์ง๊ธ ๊ฐ์ฅ ๋จ๊ฑฐ์ด 'ํธ๋ ๋ ํค์๋'๋ฅผ ์ ๋ชฉ์ ํฌํจํ๋๊ฐ? | | |
| | **4. ์ฝํ ์ธ ๊น์ด** | ๋ ์๊ฐ ์๊ฐ์ ํฌ์ํ ๋งํ ๊น์ด์ ์ ๋ฌธ์ฑ์ ๊ฐ์ถ ๋กฑํผ ์ฝํ ์ธ ์ธ๊ฐ? | | |
| | **5. ๋ฐํ ์์ ** | ๊ฐ์ฅ ์ค์ํ ๊ธฐ์ฌ๋ฅผ 'ํ๋ผ์ ํ์'์ธ ์์์ผ ์ค์ ์ ๋ฐํํ๋๊ฐ? | | |
| """ | |
| report_path = f'{output_dir}/high_view_focused_analysis_report_v2.txt' | |
| with open(report_path, 'w', encoding='utf-8') as f: | |
| f.write(report) | |
| print(f"\n - ์ข ํฉ ์ธ์ฌ์ดํธ ๋ณด๊ณ ์(v2) ์์ฑ ์๋ฃ. ({report_path} ์ ์ฅ)") | |
| # 5. ๋ฉ์ธ ์คํ ํจ์ | |
| def main(): | |
| print("===== ์ ๋ฌธ๊ณผ๋ฐฉ์ก ๋ ์ ๋ฐ์ดํฐ ์ฌ์ธต ๋ถ์ (์กฐํ์ ์ค์ฌ ์ฑ๊ณต ๊ณต์ v2) =====") | |
| data_dir, output_dir = setup_environment() | |
| df_merged = load_and_preprocess_data(data_dir) | |
| top_20, cat_comp = analyze_high_view_articles_v2(df_merged, output_dir) | |
| generate_insights_report_v2(top_20, cat_comp, output_dir) | |
| print("\n===== ๋ชจ๋ ๋ถ์์ด ์ฑ๊ณต์ ์ผ๋ก ์๋ฃ๋์์ต๋๋ค. =====") | |
| print(f"๊ฒฐ๊ณผ๋ฌผ์ '{output_dir}' ํด๋์์ ํ์ธํ์ค ์ ์์ต๋๋ค.") | |
| if __name__ == '__main__': | |
| main() |