////////
Search
Duplicate

가설 검증_노시현

Tags
Archive
ETA
2025/07/15
Main Task
Sub Task
담당자
메모
상태
Done
생성 일시
2025/07/15 00:39
우선 순위
High
진행률 %
Task : 가설 설정

테이블

 우수 숙소와 그렇지 않은 숙소의 비교

가설 설정 1 : 우수 숙소는 비인기 숙소에 비해 ‘월별 예상 매출’이 높을 것이다.

귀무가설 (H₀) 상위 25% 숙소(우수 숙소)의 월 예상 매출과 하위 25% 숙소(비우수 숙소)의 월 예상 매출은 같다.
대립가설 (H₁) 상위 25% 숙소(우수 숙소)의 월 예상 매출과 하위 25% 숙소(비우수 숙소)의 월 예상 매출은 같다.
expected_monthly_revenue 등 컬럼 활용
우수 숙소와 비인기 숙소의 비교 필요성 느낌!
예외상황이 있는지 확인하고 싶음!
두 집단을 추가로 비교 분석할 수 있는 컬럼
숙소 위치 (지역, 세부 지역, 도심/외곽)
공간 유형
요구되는 최소 숙박 일수
전체 리뷰 수, 평균 한달 리뷰 수, 최근 리뷰 날짜
호스트 리스팅 개수
예약 가능 일수
숙소 운영 일수
가설 검증

상위 숙소랑 하위 숙소는 한 달 매출에서 차이가 많이 날까? 얼마나 날까?

combinations = [ ('도심', 'Entire home/apt'), ('도심', 'Private room'), ('도심', 'Shared room'), ('외곽', 'Entire home/apt'), ('외곽', 'Private room'), ('외곽', 'Shared room'), ] results = [] for city_and_suburb, room in combinations: subset = df_filtered[ (df_filtered['city_and_suburb'] == city_and_suburb) & (df_filtered['room_type'] == room) ] # 조합별 25%, 75% 사분위수 q1 = subset['popularity_score'].quantile(0.25) q3 = subset['popularity_score'].quantile(0.75) # 상위/하위 25% upper = subset[subset['popularity_score'] >= q3]['expected_monthly_revenue'] lower = subset[subset['popularity_score'] <= q1]['expected_monthly_revenue'] # 결과 저장 results.append({ '조합': f"{city_and_suburb} & {room}", '전체 수': len(subset), '상위 25% 수': len(upper), '하위 25% 수': len(lower), '상위 25% 월 매출 중앙값': round(upper.median(), 2), '하위 25% 월 매출 중앙값': round(lower.median(), 2), '상위 25% 월 매출 평균': round(upper.mean(), 2), '하위 25% 월 매출 평균': round(lower.mean(), 2) }) expected_monthly_revenue_25 = pd.DataFrame(results) expected_monthly_revenue_25
Python
복사

그래프로 한 번 ‘중앙값’을 시각화 해볼까?

plt.figure(figsize=(10, 6)) # 데이터를 long-format으로 만들어야 seaborn에서 그리기 좋음 melted = expected_monthly_revenue_25.melt( id_vars='조합', value_vars=['상위 25% 월 매출 중앙값', '하위 25% 월 매출 중앙값'], var_name='그룹', value_name='중앙값' ) sns.barplot(data=melted, x='조합', y='중앙값', hue='그룹') plt.xticks(rotation=45) plt.title('조합별 상위/하위 25% 월 매출 중앙값 비교') plt.ylabel('월 매출 중앙값') plt.xlabel('') plt.tight_layout() plt.show()
Python
복사

 평균이랑 같이 보고 싶은데?

melted = expected_monthly_revenue_25.melt( id_vars='조합', value_vars=['상위 25% 월 매출 중앙값', '하위 25% 월 매출 중앙값', '상위 25% 월 매출 평균', '하위 25% 월 매출 평균'], var_name='그룹', value_name='월 매출' ) # 지표종류 파생 melted['지표'] = melted['그룹'].apply(lambda x: '평균' if '평균' in x else '중앙값') melted['상하위'] = melted['그룹'].apply(lambda x: '상위' if '상위' in x else '하위') plt.figure(figsize=(12, 6)) sns.lineplot( data=melted, x='조합', y='월 매출', hue='상하위', style='지표', markers=True ) plt.xticks(rotation=45) plt.title('상위/하위 25% 월 매출 (평균/중앙값)') plt.ylabel('예상 월 매출') plt.xlabel('') plt.tight_layout() plt.show()
Python
복사

그냥 봐도 차이가 나긴 하지만 한 번 검증을 해볼까?

from scipy.stats import shapiro, levene, ttest_ind, mannwhitneyu combinations = [ ('도심', 'Entire home/apt'), ('도심', 'Private room'), ('도심', 'Shared room'), ('외곽', 'Entire home/apt'), ('외곽', 'Private room'), ('외곽', 'Shared room'), ] results = [] for city_and_suburb, room in combinations: subset = df_filtered[ (df_filtered['city_and_suburb'] == city_and_suburb) & (df_filtered['room_type'] == room) ] # 조합별 25%, 75% 사분위수 q1 = subset['popularity_score'].quantile(0.25) q3 = subset['popularity_score'].quantile(0.75) # 상위/하위 25% upper = subset[subset['popularity_score'] >= q3]['expected_monthly_revenue'] lower = subset[subset['popularity_score'] <= q1]['expected_monthly_revenue'] # 정규성 s_upper = shapiro(upper) s_lower = shapiro(lower) # 등분산성 lev = levene(upper, lower) # 검정 if s_upper.pvalue > 0.05 and s_lower.pvalue > 0.05: test_type = 't-test' t_stat, p_val = ttest_ind(upper, lower, equal_var=(lev.pvalue > 0.05)) else: test_type = 'Mann-Whitney' u_stat, p_val = mannwhitneyu(upper, lower, alternative='greater') # 저장 results.append({ '조합': f"{city_and_suburb} & {room}", '전체 수': len(subset), '상위 수': len(upper), '하위 수': len(lower), '상위 평균': round(upper.mean(), 2), '하위 평균': round(lower.mean(), 2), '상위 중앙값': round(upper.median(), 2), '하위 중앙값': round(lower.median(), 2), '정규성 상위 p': round(s_upper.pvalue, 4), '정규성 하위 p': round(s_lower.pvalue, 4), '등분산 p': round(lev.pvalue, 4), '검정': test_type, 'p-value': round(p_val, 5) }) test_result = pd.DataFrame(results) test_result
Python
복사
p-value가 유의수준인 0.05보다 작은 0에 가까운 숫자이니 귀무가설 기각! 대립가설 채택!
"상위 25% 숙소는 하위 25% 숙소보다 월 예상 매출이 통계적으로 유의하게 높다.”

데이터를 통해 확인해 볼 수 있는 것

 평균과 중앙값의 차이
 평균은 이상치의 영향을 크게 받으니 중앙값 기준으로 보면 좋을 듯!
 상위와 하위의 예상 월 매출 차이
 도심 + 전체실 격차가 가장 큼
→ 상위 숙소일수록 + 전체실일 때 수익 높은 편
= 예상 월 매출은 접근성(도심)과 객실의 독립성(전체실)이 중요
 상/하위 모두 공유실의 예상 월 매출이 낮음
 공유실은 데이터가 상/하위 도심: 92개 & 외곽 119개로 값이 너무 적기 때문이라 판단
 도심과 외곽 중 도심의 가격대가 전부 높음
→ 접근성이 주는 요소가 크다고 볼 수 있음

그럼 기준이 같은 우수 숙소랑 비우수 숙소에서의 비교가 필요하겠네?

가장 차이가 큰 : 도심 + entire_home/apt

# 도심 & Entire home/apt 만 필터링 subset = df_filtered[ (df_filtered['city_and_suburb'] == '도심') & (df_filtered['room_type'] == 'Entire home/apt') ] # 상위/하위 25% popularity_score 기준 q1 = subset['popularity_score'].quantile(0.25) q3 = subset['popularity_score'].quantile(0.75) upper = subset[subset['popularity_score'] >= q3] lower = subset[subset['popularity_score'] <= q1] results = { '지표': [], '상위 25% 평균': [], '하위 25% 평균': [], '상위 25% 중앙값': [], '하위 25% 중앙값': [] } columns_to_compare = [ 'price', 'expected_monthly_revenue', 'number_of_reviews', 'reviews_per_month', 'availability_365', 'minimum_nights', 'calculated_host_listings_count', 'operating_months' ] for col in columns_to_compare: results['지표'].append(col) results['상위 25% 평균'].append(round(upper[col].mean(), 2)) results['하위 25% 평균'].append(round(lower[col].mean(), 2)) results['상위 25% 중앙값'].append(round(upper[col].median(), 2)) results['하위 25% 중앙값'].append(round(lower[col].median(), 2)) comparison_table = pd.DataFrame(results) comparison_table
Plain Text
복사

인사이트 도출

 price와 expected_monthly_revenue 컬럼을 봤을 때!
평균 가격이나 중앙값으로는 상/하위의 큰 차이가 있다고 보기 어려움
근데 상위 25%가 하위 25%보다 월 예상 매출이 약 10배 높음
→ 가격이 높아도 사람들이 잘 이용하므로 가격을 낮추는 것을 방안으로 내세우기 어려움
 number_of_reviews & rivews_per_month 컬럼을 봤을 때!
전체 리뷰 수는 상위 25%가 약 9-10배로 월등히 높음
한 달 평균 리뷰 수는 하위 25%가 거의 없는 수준
→ 하위 25%의 리뷰를 올리는 방안 모색 필요 O
️ 최근 날짜와 함께 비교할 필요 O
 availavility 365 컬럼을 봤을 때!
<7일 이하 (단기)>
<8-30일 (중기)>
<31일 이상 (장기)>
상위 평균 및 중앙값은 151-154로 비슷
→ 0에 가까울수록 우수 숙소라 생각했던 걸 깨버림….
→ 장기로 넘어갈수록 예약 가능일 수  
= 연중 운영일 많음
하위 평균은 42 중앙값은 0으로 평균과 중앙값 차이
→ 하위 25% 는 7일 이하의 숙소  
→ 운영 중단 / 비수기 운영 X / 특정 계절 운영 등 다양한 이유로 예약 가능일 수
⇒ 예약 관리, 공실률 , 비수기 운영 전략 등의 방안 제안
 minimum_nights 컬럼을 봤을 때!
평균을 보면 2배 정도 차이로 상위 25%의 기간이 짧음
→ 최소 숙박일 수를 단기로 하여 유동적으로 운영
 calculated_host_listings_count 컬럼을 봤을 때!
중앙값이 1로 같은 것을 보면 이상치의 영향 큰 것으로 판단!
 operating_months 컬럼을 봤을 때!
상위 25% 숙소 운영일 수 > 하위 25%
추가 분석
*제가 메인으로 세운 가설이 너무 당연하게 나와서…! ㅜ 우수 비우수를 컬럼별로 저희가 세운 기준에 맞춰 분석을 추가로 해보고 있습니다!

상위 숙소랑 하위 숙소는 예약 가능한 날짜 수에 따라 차이가 있을까?

최소 숙박일 수에서 나눈 단기/중기/장기에 따라 구분

상위 숙소랑 하위 숙소의 호스트 리스팅 개수도 차이가 있을까?

# 조합 정의 combinations = [ ('도심', 'Entire home/apt'), ('도심', 'Private room'), ('도심', 'Shared room'), ('외곽', 'Entire home/apt'), ('외곽', 'Private room'), ('외곽', 'Shared room'), ] results = [] # 각 조합별로 상/하위 25%에서 단기/중기/장기 숙소 수 세기 for city_and_suburb, room in combinations: cond = (df_filtered['city_and_suburb'] == city_and_suburb) & (df_filtered['room_type'] == room) subset = df_filtered[cond] q3 = subset['popularity_score'].quantile(0.75) q1 = subset['popularity_score'].quantile(0.25) upper = subset[subset['popularity_score'] >= q3] lower = subset[subset['popularity_score'] <= q1] # 예약 가능일 기준 단기/중기/장기 조건 upper_general = upper[upper['calculated_host_listings_count'] <= 2] upper_mid_pro = upper[(upper['calculated_host_listings_count'] > 2) & (upper['calculated_host_listings_count'] <= 10)] upper_pro = upper[upper['calculated_host_listings_count'] > 10] lower_general = lower[lower['calculated_host_listings_count'] <= 2] lower_mid_pro = lower[(lower['calculated_host_listings_count'] > 2) & (lower['calculated_host_listings_count'] <= 10)] lower_pro = lower[lower['calculated_host_listings_count'] > 10] results.append({ '조합': f"{city_and_suburb} & {room}", '상위_일반 호스트(~2개)': len(upper_general), '상위_준전문가(2~10개)': len(upper_mid_pro), '상위_전문가(10개~)': len(upper_pro), '하위_일반 호스트(~2개)': len(lower_general), '하위_준전문가(2~10개)': len(lower_mid_pro), '하위_전문가(10개~)': len(lower_pro), '전체 수': len(subset) }) # 데이터프레임으로 변환 range_host_count = pd.DataFrame(results) range_host_count # 시각화 진행 melted = range_host_count.melt( id_vars='조합', value_vars=['상위_일반 호스트(~2개)', '상위_준전문가(2~10개)', '상위_전문가(10개~)', '하위_일반 호스트(~2개)', '하위_준전문가(2~10개)', '하위_전문가(10개~)'], var_name='그룹', value_name='호스트 숙소 리스팅 개수' ) melted['지표종류'] = melted['그룹'].apply( lambda x: '일반 호스트(~2개)' if '일반 호스트' in x else ( '준전문가(2~10개)' if '준전문가' in x else '상위_전문가(10개~)' ) ) # FacetGrid 로 평균/중앙값 따로 표시 g = sns.catplot( data=melted, kind='bar', x='조합', y='호스트 숙소 리스팅 개수', hue='그룹', col='지표종류', sharey=False, errorbar=None, height=7, aspect=1.5 ) g.set_titles('{col_name}') g.set_xticklabels(rotation=45) g.set_axis_labels('', '호스트 숙소 리스팅 개수') g.fig.suptitle('상위/하위 25% 호스트 숙소 리스팅 개수 비교', fontsize=16) plt.tight_layout() plt.show()
Python
복사