2025.07.02 본_캠프 94일차

민동·2025년 7월 2일
1

본캠프

목록 보기
73/74

새로운 X데이터로 통계 분석을 해보자

전처리 하기

import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler

# 1) 데이터 불러오기
df = pd.read_excel('수정본.xlsx')

# 2) 0 → NaN, NaN → 평균 처리할 컬럼 목록 (6개)
cols = ['지하수_수위_최대차', '지하수_수위_변동성',
        '지하수_수온_최대차', '지하수_수온_변동성',
        '지하수_EC_최대차', '지하수_EC_변동성']

df[cols] = df[cols].replace(0, np.nan)
df[cols] = df[cols].fillna(df[cols].mean())

cols2 = ['사고당일_최저기온','사고당일_최고기온','사고당일_일강우량',
         '사고당일 일교차(℃)','강수량 합계(mm)','사고 전 3일간 일교차 평균(℃)']
df[cols2] = df[cols2].fillna(df[cols2].mean())

# 3) 사고발생일자 → 사고_월 (범주형 처리 포함)
if '사고발생일자' in df.columns:
    df['사고발생일자'] = pd.to_datetime(df['사고발생일자'])
    df['사고_월'] = df['사고발생일자'].dt.month
    df.drop('사고발생일자', axis=1, inplace=True)

    # '사고_월'을 문자열로 변환 후 원-핫 인코딩
    df['사고_월'] = df['사고_월'].astype(str)
    df = pd.get_dummies(df, columns=['사고_월'], prefix='월')
else:
    print("경고: '사고발생일자' 컬럼이 데이터프레임에 없습니다. '사고_월' 생성 및 컬럼 드롭을 건너뜁니다.")

# 4) 범주형 원-핫 인코딩 ('지층 단순화' 컬럼을 가정)
if '지층 단순화' in df.columns:
    df = pd.get_dummies(df, columns=['지층 단순화'], prefix='지층')
else:
    print("경고: '지층 단순화' 컬럼이 데이터프레임에 없습니다. 원-핫 인코딩을 건너뜁니다.")

# 5) 선택적으로 수치형 컬럼만 골라서 표준화
cols_to_standardize = [
    '지하수_수위_최대차', '지하수_수위_변동성',
    '지하수_수온_최대차', '지하수_수온_변동성',
    '지하수_EC_최대차', '지하수_EC_변동성',
    '사고당일_최저기온', '사고당일_최고기온', '사고당일_일강우량',
    '사고당일 일교차(℃)', '강수량 합계(mm)', '사고 전 3일간 일교차 평균(℃)',
    '지하철_노선_거리', '시설물_전체시설물_최단거리(m)',
    '시설물_복개구조물_종합_최단거리(m)', '시설물_지하차도_1종_최단거리(m)',
    '시설물_지하차도_2종_최단거리(m)', '시설물_지하차도_3종_최단거리(m)',
    '시설물_대형건축물_1종_최단거리(m)', '시설물_대형건축물_2종_최단거리(m)',
    '시설물_다중이용건축물_2종_최단거리(m)', '시설물_지하도상가_1종_최단거리(m)',
    '시설물_지하도상가_2종_최단거리(m)',
    '건축_진행중_최단거리(m)', '건축_완공후_최단거리(m)',
    '지하철_최단거리(m)'
]

# 실제로 표준화할 컬럼만 필터링합니다. (데이터프레임에 존재하는 컬럼만 선택)
cols_to_standardize_existing = [col for col in cols_to_standardize if col in df.columns]

# StandardScaler 인스턴스 생성
scaler = StandardScaler()

# 선택된 컬럼에만 표준화 적용
df[cols_to_standardize_existing] = scaler.fit_transform(df[cols_to_standardize_existing])

# 6) 결과 확인
df.head()

t-test 분석


import pandas as pd
import numpy as np
from scipy.stats import ttest_ind  

group_sinkhole_occurred = df[df['싱크홀_발생'] == 1]
group_sinkhole_not_occurred = df[df['싱크홀_발생'] == 0]

# t-test를 수행할 수치형 컬럼 목록 추출
# Make sure to exclude '싱크홀_발생' and '지층_' columns which are dummy variables or the target
numeric_cols_for_ttest = [col for col in df.select_dtypes(include=[np.number]).columns 
                          if col != '싱크홀_발생' and not col.startswith('지층_')]

t_results = []
for col in numeric_cols_for_ttest:
    # Dropna is applied to each group's column to ensure no NaNs in the t-test input
    a = group_sinkhole_occurred[col].dropna()
    b = group_sinkhole_not_occurred[col].dropna()
    
    # Check if there are enough samples after dropping NaNs for t-test
    if len(a) < 2 or len(b) < 2:
        print(f"경고: '{col}' 변수는 충분한 데이터가 없어 t-test를 수행할 수 없습니다.")
        continue # Skip this variable if not enough data
    
    # Perform Welch's t-test
    t_stat, p_val = ttest_ind(a, b, equal_var=False) 
    
    # *** Crucial Fix: Check for NaN p_val before formatting ***
    # This prevents the 'ValueError: Unable to parse string "nan"'
    if pd.isna(p_val):
        print(f"경고: '{col}' 변수의 t-test 결과 p-value가 NaN입니다. 이 변수는 결과에 포함되지 않습니다.")
        continue # Skip this column as its p-value is not valid or calculable

    # Determine significance star
    significance_star = ""
    if p_val < 0.05:
        significance_star = "*" # Add asterisk if p-value is less than 0.05

    t_results.append({
        '변수': col,
        '싱크홀_발생_평균': f"{a.mean():.4f}",
        '싱크홀_미발생_평균': f"{b.mean():.4f}",
        't-통계량': f"{t_stat:.4f}",
        'p-value': f"{p_val:.4f}", # p-value as a string for display
        '유의성': significance_star # Asterisk only
    })

t_df = pd.DataFrame(t_results)

# Convert 'p-value' to numeric for sorting. Use errors='coerce' as a safeguard,
# though the explicit NaN check above should prevent problematic "nan" strings.
if not t_df.empty: # Ensure t_df is not empty before trying to process
    t_df['p-value_numeric'] = pd.to_numeric(t_df['p-value'], errors='coerce')
    # Sort by the numeric p-value and then drop the temporary numeric column
    t_df = t_df.sort_values(by='p-value_numeric').drop(columns='p-value_numeric')
else:
    print("경고: t-test를 수행할 변수가 없거나 모든 변수에서 유효한 결과가 나오지 않아 결과 DataFrame이 비어있습니다.")


print("---")
print("### Welch's t-test 결과 (싱크홀 발생: 1 vs 0)")
print("*(유의성 컬럼의 '*'는 p < 0.05를 의미합니다.)")
if not t_df.empty:
    print(t_df.to_string(index=False))
else:
    print("결과를 표시할 데이터가 없습니다.")

# --- 유의미한 변수 이름 추출 및 출력 ---
print("\n---")
print("### 통계적으로 유의미한 변수 (p < 0.05) 목록:")

if not t_df.empty:
    significant_vars = t_df[t_df['유의성'] == '*']['변수'].tolist()

    if significant_vars:
        for var in significant_vars:
            print(f"- {var}")
    else:
        print("통계적으로 유의미한 변수가 없습니다 (p < 0.05 기준).")
else:
    print("t-test 결과 데이터가 없어 유의미한 변수를 확인할 수 없습니다.")
  
  변수	싱크홀_발생_평균	싱크홀_미발생_평균	t-통계량	p-value	유의성
34	노선_반경내_개수_180m	0.9278	0.0636	7.8041	0.0000	*
32	지하철역_반경내_개수_180m	0.2990	0.0136	5.1217	0.0000	*
31	지하철역_최단거리(m)	471.0958	763.0763	-6.1859	0.0000	*
30	지하철역_반경내_개수_400m	0.5773	0.1500	5.4793	0.0000	*
7	시설물_전체시설물_최단거리(m)	-0.3398	0.1498	-4.2044	0.0000	*
19	시설물_지하도상가_2종_최단거리(m)	-0.3437	0.1516	-4.2672	0.0000	*
33	노선_거리(m)	349.3234	784.2394	-7.2346	0.0000	*
15	시설물_대형건축물_2종_최단거리(m)	-0.3612	0.1593	-4.5736	0.0000	*
13	시설물_대형건축물_1종_최단거리(m)	-0.3943	0.1738	-4.9033	0.0000	*
22	건축_완공후_개수(180m)	1.0515	1.9409	-4.0671	0.0001	*
11	시설물_지하차도_3종_최단거리(m)	-0.2998	0.1322	-3.9999	0.0001	*
6	시설물_전체시설물_반경내_개수_170m	1.1753	0.4091	3.8858	0.0002	*
26	사고당일_일강우량	0.3781	-0.1667	3.6137	0.0004	*
28	강수량 합계(mm)	0.3246	-0.1431	3.5790	0.0005	*
17	시설물_다중이용건축물_2종_최단거리(m)	-0.2649	0.1168	-3.1625	0.0018	*
10	시설물_지하차도_2종_최단거리(m)	-0.2598	0.1146	-3.1487	0.0019	*
18	시설물_지하도상가_1종_최단거리(m)	-0.2581	0.1138	-3.1377	0.0020	*
12	시설물_대형건축물_1종_반경내_개수_170m	0.2887	0.0136	3.1610	0.0021	*
23	건축_완공후_최단거리(m)	0.2773	-0.1222	3.0637	0.0026	*
14	시설물_대형건축물_2종_반경내_개수_170m	0.3814	0.0409	3.0388	0.0030	*
25	사고당일_최고기온	0.2031	-0.0895	2.5425	0.0117	*
16	시설물_다중이용건축물_2종_반경내_개수_170m	0.1959	0.0500	2.5507	0.0120	*
9	시설물_지하차도_1종_최단거리(m)	-0.1704	0.0751	-2.4081	0.0167	*
24	사고당일_최저기온	0.1839	-0.0811	2.1732	0.0311	*
4	지하수_EC_최대차	-0.1017	0.0448	-1.4769	0.1407	
5	지하수_EC_변동성	-0.0901	0.0397	-1.2948	0.1964	
8	시설물_복개구조물_종합_최단거리(m)	0.1110	-0.0489	1.2491	0.2134	
29	사고 전 3일간 일교차 평균()	0.0817	-0.0360	1.1452	0.2531	
27	사고당일 일교차()	0.0597	-0.0263	0.8068	0.4205	
0	지하수_수위_최대차	-0.0415	0.0183	-0.5828	0.5605	
1	지하수_수위_변동성	-0.0396	0.0175	-0.5730	0.5671	
20	건축_진행중_개수(180m)	0.0619	0.0818	-0.5565	0.5785	
21	건축_진행중_최단거리(m)	0.0238	-0.0105	0.3207	0.7487	
3	지하수_수온_변동성	0.0190	-0.0084	0.2262	0.8213	
2	지하수_수온_최대차	0.0023	-0.0010	0.0285	0.9773	

범주형

import pandas as pd
import numpy as np
from scipy.stats import chi2_contingency

# 1) 원본 데이터 로드
df = pd.read_excel('수정본.xlsx')

# 2) 싱크홀_발생을 정수형(0/1)으로
df['싱크홀_발생'] = df['싱크홀_발생'].astype(int)

# 3) 사고발생일자에서 월 추출
df['사고발생일자'] = pd.to_datetime(df['사고발생일자'])
df['사고_월'] = df['사고발생일자'].dt.month

# --- (A) 지층 단순화 vs 싱크홀 발생 ---
cont_table_geo = pd.crosstab(df['지층 단순화'], df['싱크홀_발생'])
chi2_geo, p_geo, dof_geo, exp_geo = chi2_contingency(cont_table_geo, correction=False)

print("=== 지층 단순화 vs 싱크홀 발생 ===")
print(f"Chi2 = {chi2_geo:.4f}, p-value = {p_geo:.4f} {'*' if p_geo<0.05 else ''}, dof = {dof_geo}\n")
print("관측빈도:\n", cont_table_geo, "\n")
print("기대빈도:\n", pd.DataFrame(exp_geo, index=cont_table_geo.index, columns=cont_table_geo.columns), "\n")
print(("지층 단순화와 싱크홀 발생은 "
      + ("유의미한 연관성이 있습니다." if p_geo<0.05 else "연관성이 있다고 보기 어렵습니다.")))
print("\n" + "-"*50 + "\n")

# --- (B) 사고_월 vs 싱크홀 발생 ---
cont_table_month = pd.crosstab(df['사고_월'], df['싱크홀_발생'])
chi2_mon, p_mon, dof_mon, exp_mon = chi2_contingency(cont_table_month, correction=False)

print("=== 사고_월 vs 싱크홀 발생 ===")
print(f"Chi2 = {chi2_mon:.4f}, p-value = {p_mon:.4f} {'*' if p_mon<0.05 else ''}, dof = {dof_mon}\n")
print("관측빈도:\n", cont_table_month, "\n")
print("기대빈도:\n", pd.DataFrame(exp_mon, index=cont_table_month.index, columns=cont_table_month.columns), "\n")
print(("사고_월과 싱크홀 발생은 "
      + ("유의미한 연관성이 있습니다." if p_mon<0.05 else "연관성이 있다고 보기 어렵습니다.")))
  
=== 지층 단순화 vs 싱크홀 발생 ===
Chi2 = 23.8663, p-value = 0.0001 *, dof = 4

관측빈도:
싱크홀_발생   0   1
지층 단순화        
운모편암     9   0
충적층     43  33
편마암     97  37
홍적층      0   5
화강암     71  22 

기대빈도:
싱크홀_발생          0          1
지층 단순화                      
운모편암     6.246057   2.753943
충적층     52.744479  23.255521
편마암     92.996845  41.003155
홍적층      3.470032   1.529968
화강암     64.542587  28.457413 

지층 단순화와 싱크홀 발생은 유의미한 연관성이 있습니다.

--------------------------------------------------

=== 사고_월 vs 싱크홀 발생 ===
Chi2 = 20.1892, p-value = 0.0428 *, dof = 11

관측빈도:
싱크홀_발생   0   1
사고_월          
1       23  10
2       20   4
3       14  12
4       25   8
5       12   5
6       16   9
7       30  12
8       19  20
9       11   7
10      21   4
11      13   3
12      16   3 

기대빈도:
싱크홀_발생          0          1
사고_월                        
1       22.902208  10.097792
2       16.656151   7.343849
3       18.044164   7.955836
4       22.902208  10.097792
5       11.798107   5.201893
6       17.350158   7.649842
7       29.148265  12.851735
8       27.066246  11.933754
9       12.492114   5.507886
10      17.350158   7.649842
11      11.104101   4.895899
12      13.186120   5.813880 

사고_월과 싱크홀 발생은 유의미한 연관성이 있습니다.

지층 심층 분석


import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from scipy.stats import chi2_contingency # chi2_contingency 함수 임포트

# 1) 데이터 불러오기 (제공된 전처리 코드 사용)
df = pd.read_excel('수정본.xlsx')

# 2) 0 → NaN, NaN → 평균 처리할 컬럼 목록 (6개)
cols = ['지하수_수위_최대차', '지하수_수위_변동성',
        '지하수_수온_최대차', '지하수_수온_변동성',
        '지하수_EC_최대차', '지하수_EC_변동성']

df[cols] = df[cols].replace(0, np.nan)     
df[cols] = df[cols].fillna(df[cols].mean())  

cols2 = ['사고당일_최저기온','사고당일_최고기온','사고당일_일강우량','사고당일 일교차(℃)','강수량 합계(mm)','사고 전 3일간 일교차 평균(℃)']
df[cols2] = df[cols2].fillna(df[cols2].mean())  

# 3) 사고발생일자 → 사고_월
df['사고발생일자'] = pd.to_datetime(df['사고발생일자'])
df['사고_월'] = df['사고발생일자'].dt.month
df.drop('사고발생일자', axis=1, inplace=True)

# 4) 범주형 원-핫 인코딩 ('지층 단순화' 컬럼을 가정)
df = pd.get_dummies(df, columns=['지층 단순화'], prefix='지층')

# 5) 수치형 컬럼만 골라서 표준화
numeric_cols_before_drop = df.select_dtypes(include=[np.number]).columns.tolist()
if '사고발생위치' in numeric_cols_before_drop:
    numeric_cols_before_drop.remove('사고발생위치')

scaler = StandardScaler()사고_월과 싱크홀 발생은 유의미한 연관성이 있습니다.

df[numeric_cols_before_drop] = scaler.fit_transform(df[numeric_cols_before_drop])

# 6) 결과 확인: '사고발생위치' 컬럼 제거
#df = df.drop(columns='사고발생위치')

# --- 카이제곱 독립성 검정 코드 시작 ---

# 싱크홀 발생 컬럼을 int 타입으로 변환 (0 또는 1)
df['싱크홀_발생'] = df['싱크홀_발생'].astype(int)

# 카이제곱 검정을 수행할 '지층_' 관련 컬럼 목록 추출
geological_layer_cols = [col for col in df.columns if col.startswith('지층_')]

chi2_results = []
for col in geological_layer_cols:
    # 교차표 (Contingency Table) 생성
    # index는 '싱크홀_발생', columns는 각 '지층_' 컬럼
    cont_table = pd.crosstab(df['싱크홀_발생'], df[col])
    
    # 카이제곱 검정 수행
    # correction=False는 Yates' correction을 적용하지 않음을 의미 (일반적으로 큰 데이터셋에서는 False)
    chi2, p, dof, exp = chi2_contingency(cont_table, correction=False)
    
    # p-value 유의성 표시 (0.05 기준)
    significance_star = ""
    if p < 0.05:
        significance_star = "*" # 0.05 미만일 경우 별표 표시

    chi2_results.append({
        '변수': col,
        '카이제곱 통계량': f"{chi2:.4f}",
        '자유도': dof,
        'p-value': f"{p:.4f}",
        '유의성': significance_star
    })

chi2_df = pd.DataFrame(chi2_results)

# p-value 기준으로 오름차순 정렬
chi2_df['p-value_numeric'] = pd.to_numeric(chi2_df['p-value'])
chi2_df = chi2_df.sort_values(by='p-value_numeric').drop(columns='p-value_numeric')

print("---")
print("### 카이제곱 독립성 검정 결과 (지층 vs 싱크홀 발생)")
print("*(유의성 컬럼의 '*'는 p < 0.05를 의미합니다.)")
print(chi2_df.to_string(index=False))

# --- 유의미한 지층 변수 이름 추출 및 출력 ---
print("\n---")
print("### 통계적으로 유의미한 지층 변수 (p < 0.05) 목록:")

significant_geological_vars = chi2_df[chi2_df['유의성'] == '*']['변수'].tolist()

if significant_geological_vars:
    for var in significant_geological_vars:
        print(f"- {var.replace('지층_', '')}") # '지층_' 접두사 제거하여 출력
else:
    print("싱크홀 발생과 통계적으로 유의미한 연관성을 보이는 지층 변수가 없습니다 (p < 0.05 기준).")
 
---
### 카이제곱 독립성 검정 결과 (지층 vs 싱크홀 발생)
*(유의성 컬럼의 '*'는 p < 0.05를 의미합니다.)
     변수 카이제곱 통계량  자유도 p-value 유의성
 지층_홍적층  11.5219    1  0.0007   *
 지층_충적층   7.7387    1  0.0054   *
지층_운모편암   4.0841    1  0.0433   *
 지층_화강암   2.9879    1  0.0839    
 지층_편마암   0.9755    1  0.3233    

---
### 통계적으로 유의미한 지층 변수 (p < 0.05) 목록:
- 홍적층
- 충적층
- 운모편암
profile
아자아자

2개의 댓글

comment-user-thumbnail
2025년 7월 2일

?

1개의 답글