chuyển đổi cấu trúc
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 438 KiB |
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"Age": {
|
||||
"Mean": 35.25316455696203,
|
||||
"Std": 16.342855691857576,
|
||||
"Min": 19.0,
|
||||
"Max": 79.0,
|
||||
"Shapiro-Wilk_W": 0.8454128859887641,
|
||||
"Shapiro-Wilk_p": 1.310419664520166e-07,
|
||||
"Normal_Distribution": false
|
||||
},
|
||||
"Gender": {
|
||||
"2": 55.0,
|
||||
"1": 44.3,
|
||||
"3": 0.7
|
||||
},
|
||||
"Income": {
|
||||
"439.9999999999999": 15.5,
|
||||
"586.6666666666666": 8.0,
|
||||
"220.0": 6.5,
|
||||
"330.0": 4.5,
|
||||
"550.0": 4.5,
|
||||
"293.3333333333333": 4.0,
|
||||
"264.0": 4.0,
|
||||
"660.0": 4.0,
|
||||
"132.0": 3.5,
|
||||
"165.0": 3.0,
|
||||
"176.0": 3.0,
|
||||
"528.0": 3.0,
|
||||
"733.3333333333334": 2.5,
|
||||
"33.0": 2.5,
|
||||
"385.0": 2.5,
|
||||
"110.0": 2.0,
|
||||
"275.0": 2.0,
|
||||
"366.6666666666667": 2.0,
|
||||
"513.3333333333333": 2.0,
|
||||
"183.3333333333333": 1.5,
|
||||
"73.33333333333333": 1.5,
|
||||
"352.0": 1.5,
|
||||
"322.6666666666666": 1.0,
|
||||
"234.6666666666667": 1.0,
|
||||
"146.6666666666667": 1.0,
|
||||
"88.0": 1.0,
|
||||
"308.0": 1.0,
|
||||
"54.99999999999999": 1.0,
|
||||
"35.2": 1.0,
|
||||
"879.9999999999999": 1.0,
|
||||
"495.0": 1.0,
|
||||
"484.0": 1.0,
|
||||
"809.5999999999999": 0.5,
|
||||
"704.0": 0.5,
|
||||
"990.0": 0.5,
|
||||
"44.0": 0.5,
|
||||
"188.5714285714286": 0.5,
|
||||
"256.6666666666666": 0.5,
|
||||
"469.3333333333333": 0.5,
|
||||
"806.6666666666666": 0.5,
|
||||
"282.8571428571428": 0.5,
|
||||
"1320.0": 0.5,
|
||||
"605.0": 0.5,
|
||||
"572.0": 0.5,
|
||||
"616.0": 0.5
|
||||
},
|
||||
"Literacy": {
|
||||
"6": 43.0,
|
||||
"4": 31.9,
|
||||
"3": 16.3,
|
||||
"2": 4.6,
|
||||
"1": 2.9,
|
||||
"5": 1.3
|
||||
},
|
||||
"Career": {
|
||||
"4": 30.3,
|
||||
"1": 24.8,
|
||||
"3": 15.3,
|
||||
"2": 14.7,
|
||||
"6": 14.0,
|
||||
"5": 1.0
|
||||
},
|
||||
"Habits": {
|
||||
"Distance": {
|
||||
"1": "31.3% (n=96)",
|
||||
"2": "24.8% (n=76)",
|
||||
"3": "17.6% (n=54)",
|
||||
"4": "16.0% (n=49)",
|
||||
"5": "10.1% (n=31)",
|
||||
"6": "0.3% (n=1)"
|
||||
},
|
||||
"Frequency": {
|
||||
"4": "27.0% (n=83)",
|
||||
"5": "25.4% (n=78)",
|
||||
"2": "17.6% (n=54)",
|
||||
"1": "15.3% (n=47)",
|
||||
"3": "14.7% (n=45)"
|
||||
},
|
||||
"Time": {
|
||||
"4": "35.2% (n=108)",
|
||||
"5": "28.3% (n=87)",
|
||||
"3": "27.4% (n=84)",
|
||||
"2": "7.2% (n=22)",
|
||||
"1": "2.0% (n=6)"
|
||||
},
|
||||
"Transportation": {
|
||||
"3": "57.0% (n=175)",
|
||||
"2": "19.9% (n=61)",
|
||||
"1": "16.3% (n=50)",
|
||||
"4": "5.2% (n=16)",
|
||||
"5": "1.6% (n=5)"
|
||||
}
|
||||
},
|
||||
"Activities": {
|
||||
"Jogging": 0.21,
|
||||
"Workout": 0.35,
|
||||
"Sightseeing": 0.76,
|
||||
"Talking": 0.54,
|
||||
"Photography": 0.27
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import seaborn as sns
|
||||
from scipy import stats
|
||||
import json
|
||||
|
||||
df = pd.read_excel('Data_VN_filter_v5.xlsx')
|
||||
|
||||
# Tùy chỉnh font chữ matplotlib nếu cần
|
||||
plt.rcParams.update({'font.size': 12})
|
||||
|
||||
output_data = {}
|
||||
|
||||
# 1. Kiểm tra biến Tuổi (Age)
|
||||
if 'Age' in df.columns:
|
||||
# Convert age to numeric just in case
|
||||
age_data = pd.to_numeric(df['Age'], errors='coerce').dropna()
|
||||
stat, p = stats.shapiro(age_data)
|
||||
output_data['Age'] = {
|
||||
'Mean': age_data.mean(),
|
||||
'Std': age_data.std(),
|
||||
'Min': age_data.min(),
|
||||
'Max': age_data.max(),
|
||||
'Shapiro-Wilk_W': stat,
|
||||
'Shapiro-Wilk_p': p,
|
||||
'Normal_Distribution': bool(p > 0.05)
|
||||
}
|
||||
|
||||
# Plot Age histogram
|
||||
plt.figure(figsize=(8, 5))
|
||||
sns.histplot(age_data, kde=True, color='skyblue')
|
||||
plt.title('Phân phối Tuổi (Age)')
|
||||
plt.xlabel('Tuổi')
|
||||
plt.ylabel('Tần số')
|
||||
plt.savefig('Age_Distribution.png', dpi=300, bbox_inches='tight')
|
||||
plt.close()
|
||||
|
||||
# 2. Nhân khẩu học (Demographics)
|
||||
demo_cols = ['Gender', 'Income', 'Literacy', 'Career']
|
||||
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
|
||||
fig.suptitle('Cơ cấu Nhân khẩu học của Mẫu Khảo sát (N = 307)', fontsize=18, fontweight='bold', y=1.02)
|
||||
|
||||
for i, col in enumerate(demo_cols):
|
||||
if col in df.columns:
|
||||
ax = axes[i//2, i%2]
|
||||
val_counts = df[col].value_counts()
|
||||
|
||||
# Nếu biến có quá nhiều giá trị (như Income), vẽ Histogram thay vì Pie chart
|
||||
if len(val_counts) > 10 or col == 'Income':
|
||||
numeric_data = pd.to_numeric(df[col], errors='coerce').dropna()
|
||||
sns.histplot(numeric_data, kde=True, ax=ax, color='coral', bins=20)
|
||||
ax.set_title(f'Phân phối {col}', fontsize=14, pad=10)
|
||||
ax.set_xlabel(col)
|
||||
ax.set_ylabel('Tần số')
|
||||
else:
|
||||
# Plot Pie Chart cho biến phân loại
|
||||
wedges, texts, autotexts = ax.pie(val_counts, autopct='%1.1f%%', startangle=140,
|
||||
colors=sns.color_palette('Set2'), pctdistance=0.8)
|
||||
ax.legend(wedges, val_counts.index, title=col, loc="center left", bbox_to_anchor=(1, 0.5))
|
||||
ax.set_title(f'Tỷ lệ {col}', fontsize=14, pad=10)
|
||||
|
||||
output_data[col] = df[col].value_counts(normalize=True).mul(100).round(1).to_dict()
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig('Demographics_Charts.png', dpi=300, bbox_inches='tight')
|
||||
plt.close()
|
||||
|
||||
# 3. Thói quen tương tác (Interactions)
|
||||
habit_cols = ['Distance', 'Frequency', 'Time', 'Transportation']
|
||||
output_data['Habits'] = {}
|
||||
for col in habit_cols:
|
||||
if col in df.columns:
|
||||
counts = df[col].value_counts()
|
||||
pcts = df[col].value_counts(normalize=True).mul(100).round(1)
|
||||
output_data['Habits'][col] = {str(k): f"{pcts[k]}% (n={counts[k]})" for k in counts.index}
|
||||
|
||||
# 4. Các hoạt động phổ biến (Activities)
|
||||
# Bảng hỏi thường dùng Yes/No (1/0) hoặc Likert. Tính tỷ lệ % chọn Yes (1).
|
||||
act_cols = ['Jogging', 'Workout', 'Pet', 'Sightseeing', 'Talking', 'Photography']
|
||||
output_data['Activities'] = {}
|
||||
for col in act_cols:
|
||||
# Nếu biến là 'Pet Walking' thay vì 'Pet'
|
||||
actual_col = col if col in df.columns else (col + ' Walking' if col + ' Walking' in df.columns else None)
|
||||
if actual_col:
|
||||
mean_val = pd.to_numeric(df[actual_col], errors='coerce').mean()
|
||||
# Nếu mean < 1.5, có thể là dữ liệu nhị phân (0-1), ngược lại là Likert
|
||||
output_data['Activities'][actual_col] = round(mean_val, 2)
|
||||
|
||||
# Save summary to JSON
|
||||
with open('descriptive_output.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(output_data, f, ensure_ascii=False, indent=4)
|
||||
|
||||
print("Descriptive statistics run complete. Outputs saved.")
|
||||
@@ -0,0 +1,8 @@
|
||||
,Temperature,Noise,Stormwind,Respiratory,Exercises,Culture,Beauty,Education,Society,Spirit,Dirty,Unsafe,Danger
|
||||
Park,0.5861277411758214,0.46872135461497316,0.5433785341732876,0.552634098528652,0.6278282242144403,0.5249311710036608,0.6010523214572643,0.6698801970338424,0.592175252811864,0.5873765414225279,-0.4119222589542689,-0.36935992311052424,-0.47499404654301275
|
||||
Residential,0.610813681241285,0.5010876976294922,0.5812495058768619,0.6308055529227076,0.6905711940417876,0.5336682510392587,0.7050811390341176,0.6880894492038738,0.6281007540295365,0.6725382465666585,-0.4172763562532894,-0.3683423748854766,-0.47006223532477215
|
||||
Garden,0.45224716780106355,0.3994427050538716,0.41423108867853403,0.4171592620121273,0.5703375228779752,0.42420205471782707,0.5225523665124601,0.5586006221718542,0.4604821197705184,0.4741367465117808,-0.3021035545730409,-0.2690208910002917,-0.33928178580122825
|
||||
Rooftop,0.4960914195506356,0.4389763334105073,0.4741573996062278,0.4043299683546717,0.5234138548608293,0.40036627443478834,0.5613291264190557,0.5137072417522562,0.4507351073588117,0.533245963504779,-0.3044536381978058,-0.3287078138585324,-0.3540628872780644
|
||||
Recreation,0.40870892534299835,0.36018022246155923,0.38726035929996405,0.327640261869753,0.4435840120247182,0.41597785467253595,0.41702432907643733,0.46845721173859056,0.337735516408859,0.346506311430844,-0.08584770592904668,-0.14113338302330178,-0.2698727403572271
|
||||
Agriculture,-0.3186847619885256,-0.22410153490737583,-0.31116783542012505,-0.31164843416963245,-0.29672463363412205,-0.08295065998935172,-0.407725512606722,-0.34041202068253673,-0.33774567405476646,-0.3661505107268029,0.4906684665223075,0.4667053886536356,0.5249948486680992
|
||||
Nature,-0.4377274782376116,-0.2893202018289482,-0.47487184947631206,-0.4416794342162952,-0.4395911413850507,-0.2583285861208512,-0.4891875676625891,-0.4669790589031381,-0.4642512201471137,-0.5258378364708906,0.5182221077803194,0.5103204875306926,0.5338960402111311
|
||||
|
@@ -0,0 +1,14 @@
|
||||
,0,1
|
||||
Temperature,-0.15445511914940652,0.006990515901429791
|
||||
Noise,-0.07615894298740968,0.00700492282153885
|
||||
Stormwind,-0.1563914823055307,0.007878888704259901
|
||||
Respiratory,-0.14800873993854352,-0.011635761620414456
|
||||
Exercises,-0.16208617036601874,-0.0003806098068870642
|
||||
Culture,-0.04550439013316019,0.00881620114706321
|
||||
Beauty,-0.19796961667785237,-0.0012069882779817856
|
||||
Education,-0.17984311395007466,0.008345565782577814
|
||||
Society,-0.16523244764182207,-0.011179747262609863
|
||||
Spirit,-0.1938278638629291,-0.01479224672248243
|
||||
Dirty,0.662990083180058,0.026367956173616355
|
||||
Unsafe,0.6446258846820032,-0.0004123561994046179
|
||||
Danger,0.7399178159934315,-0.028168947040193765
|
||||
|
@@ -0,0 +1,8 @@
|
||||
,0,1
|
||||
Park,-0.20737329331501148,-0.002436125971632047
|
||||
Residential,-0.2155834258211865,-0.011730849372120936
|
||||
Garden,-0.14244079709884974,-0.004395477787898166
|
||||
Rooftop,-0.15500847340894128,-0.003399988119557015
|
||||
Recreation,-0.0682883185805637,0.027918075759378963
|
||||
Agriculture,0.5601149289877119,-0.005751109303690985
|
||||
Nature,0.6998145216538895,-0.0007509375795379177
|
||||
|
Binary file not shown.
|
After Width: | Height: | Size: 330 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 395 KiB |
@@ -0,0 +1,8 @@
|
||||
,Temperature,Noise,Stormwind,Respiratory,Exercises,Culture,Beauty,Education,Society,Spirit,Dirty,Unsafe,Danger
|
||||
Park,0.5861277411758214,0.4687213546149731,0.5433785341732876,0.552634098528652,0.6278282242144403,0.5249311710036608,0.6010523214572643,0.6698801970338424,0.592175252811864,0.5873765414225279,-0.4119222589542689,-0.3693599231105242,-0.4749940465430127
|
||||
Residential,0.610813681241285,0.5010876976294922,0.5812495058768619,0.6308055529227076,0.6905711940417876,0.5336682510392587,0.7050811390341176,0.6880894492038738,0.6281007540295365,0.6725382465666585,-0.4172763562532894,-0.3683423748854766,-0.4700622353247721
|
||||
Garden,0.4522471678010635,0.3994427050538716,0.414231088678534,0.4171592620121273,0.5703375228779752,0.424202054717827,0.5225523665124601,0.5586006221718542,0.4604821197705184,0.4741367465117808,-0.3021035545730409,-0.2690208910002917,-0.3392817858012282
|
||||
Rooftop,0.4960914195506356,0.4389763334105073,0.4741573996062278,0.4043299683546717,0.5234138548608293,0.4003662744347883,0.5613291264190557,0.5137072417522562,0.4507351073588117,0.533245963504779,-0.3044536381978058,-0.3287078138585324,-0.3540628872780644
|
||||
Recreation,0.4087089253429983,0.3601802224615592,0.387260359299964,0.327640261869753,0.4435840120247182,0.4159778546725359,0.4170243290764373,0.4684572117385905,0.337735516408859,0.346506311430844,-0.0858477059290466,-0.1411333830233017,-0.2698727403572271
|
||||
Agriculture,-0.3186847619885256,-0.2241015349073758,-0.311167835420125,-0.3116484341696324,-0.296724633634122,-0.0829506599893517,-0.407725512606722,-0.3404120206825367,-0.3377456740547664,-0.3661505107268029,0.4906684665223075,0.4667053886536356,0.5249948486680992
|
||||
Nature,-0.4377274782376116,-0.2893202018289482,-0.474871849476312,-0.4416794342162952,-0.4395911413850507,-0.2583285861208512,-0.4891875676625891,-0.4669790589031381,-0.4642512201471137,-0.5258378364708906,0.5182221077803194,0.5103204875306926,0.5338960402111311
|
||||
|
@@ -0,0 +1,93 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import prince
|
||||
|
||||
# Load data
|
||||
df = pd.read_excel('Data_VN_filter_v5.xlsx')
|
||||
|
||||
# Extract variables
|
||||
ugs_cols = ['Park', 'Residential', 'Garden', 'Rooftop', 'Recreation', 'Agriculture', 'Nature']
|
||||
|
||||
# Detailed ESS and DES variables
|
||||
ess_des_detailed = [
|
||||
# RES (Điều hòa)
|
||||
'Temperature', 'Noise', 'Stormwind', 'Respiratory',
|
||||
# CES (Văn hóa)
|
||||
'Exercises', 'Culture', 'Beauty', 'Education', 'Society', 'Spirit',
|
||||
# DES (Bất lợi)
|
||||
'Dirty', 'Unsafe', 'Danger'
|
||||
]
|
||||
|
||||
# Ensure the columns exist in df
|
||||
available_detailed = [col for col in ess_des_detailed if col in df.columns]
|
||||
|
||||
# We want to run CA on the relationships between the 7 UGS types and the 13 specific services.
|
||||
# Dữ liệu thang đo Likert thường là dữ liệu thứ bậc (Ordinal), nên dùng Spearman's rho sẽ chính xác hơn Pearson.
|
||||
data = df[ugs_cols + available_detailed].dropna()
|
||||
corr = data.corr(method='spearman').loc[ugs_cols, available_detailed]
|
||||
|
||||
# Tương tự như trước, dịch chuyển ma trận hệ số RHO để các giá trị đều dương (dùng cho CA)
|
||||
# Alternatively, since prince CA handles frequencies, we can just pass the raw data?
|
||||
# If we pass raw data of shape (307, 20), CA will treat rows as individuals.
|
||||
# We want to see relationships between UGS and Detailed Services. Passing the correlation matrix (shifted) is a good proxy for similarity.
|
||||
corr_shifted = corr + 1
|
||||
|
||||
# Initialize CA
|
||||
ca = prince.CA(n_components=2, n_iter=3, copy=True, check_input=True, engine='scipy', random_state=42)
|
||||
ca = ca.fit(corr_shifted)
|
||||
|
||||
# Extract column and row coordinates
|
||||
row_coords = ca.row_coordinates(corr_shifted) # UGS
|
||||
col_coords = ca.column_coordinates(corr_shifted) # Detailed Services
|
||||
|
||||
# Save to CSV
|
||||
corr.to_csv('CA_Detailed_Correlation_Matrix.csv')
|
||||
row_coords.to_csv('CA_Detailed_UGS_Coords.csv')
|
||||
col_coords.to_csv('CA_Detailed_Services_Coords.csv')
|
||||
|
||||
# Plot Biplot
|
||||
fig, ax = plt.subplots(figsize=(20, 15))
|
||||
|
||||
# Plot UGS points (blue dots)
|
||||
p_ugs = ax.scatter(row_coords[0], row_coords[1], c='blue', label='UGS Types', s=80, marker='o', edgecolors='black')
|
||||
|
||||
# Plot ESS/DES points (red/green squares)
|
||||
res_cols = ['Temperature', 'Noise', 'Stormwind', 'Respiratory']
|
||||
ces_cols = ['Exercises', 'Culture', 'Beauty', 'Education', 'Society', 'Spirit']
|
||||
des_cols = ['Dirty', 'Unsafe', 'Danger']
|
||||
|
||||
p_ess = []
|
||||
for col in available_detailed:
|
||||
color = 'green' if col in res_cols else ('purple' if col in ces_cols else 'red')
|
||||
p = ax.scatter(col_coords.loc[col, 0], col_coords.loc[col, 1], c=color, marker='s', s=80, edgecolors='black')
|
||||
p_ess.append(p)
|
||||
|
||||
# Gắn nhãn (Text) tĩnh để đảm bảo tính nhất quán (Deterministic) 100% mỗi lần chạy
|
||||
# Phân tách vị trí nhãn: UGS nằm lệch trên-trái, ESS/DES nằm lệch dưới-phải
|
||||
for i, txt in enumerate(ugs_cols):
|
||||
ax.annotate(txt, (row_coords.iloc[i, 0], row_coords.iloc[i, 1]),
|
||||
xytext=(-10, 10), textcoords='offset points',
|
||||
color='blue', fontweight='bold', fontsize=12, ha='right', va='bottom')
|
||||
|
||||
for col in available_detailed:
|
||||
color = 'green' if col in res_cols else ('purple' if col in ces_cols else 'red')
|
||||
ax.annotate(col, (col_coords.loc[col, 0], col_coords.loc[col, 1]),
|
||||
xytext=(10, -10), textcoords='offset points',
|
||||
color=color, fontsize=12, fontweight='bold', ha='left', va='top')
|
||||
|
||||
# Add dummy plots for legend
|
||||
ax.scatter([], [], c='green', marker='s', label='RES (Điều hòa)')
|
||||
ax.scatter([], [], c='purple', marker='s', label='CES (Văn hóa)')
|
||||
ax.scatter([], [], c='red', marker='s', label='DES (Bất lợi)')
|
||||
|
||||
ax.axhline(0, color='grey', linestyle='--', linewidth=1)
|
||||
ax.axvline(0, color='grey', linestyle='--', linewidth=1)
|
||||
ax.set_title('Detailed CA Biplot (UGS vs Specific ESS/DES)', fontsize=16)
|
||||
ax.set_xlabel('Component 0', fontsize=12)
|
||||
ax.set_ylabel('Component 1', fontsize=12)
|
||||
ax.legend(loc='center left', bbox_to_anchor=(1.02, 0.5), fontsize=12)
|
||||
plt.grid(True, linestyle=':', alpha=0.6)
|
||||
plt.savefig('CA_plot_detailed.png', dpi=300, bbox_inches='tight')
|
||||
print("\nDetailed CA Plot saved to CA_plot_detailed.png")
|
||||
print("Detailed data exported to CSV files.")
|
||||
@@ -0,0 +1,24 @@
|
||||
import pandas as pd
|
||||
import seaborn as sns
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# Đọc ma trận RHO đã tính từ file CSV
|
||||
corr = pd.read_csv('CA_Detailed_Correlation_Matrix.csv', index_col=0)
|
||||
|
||||
# Vẽ biểu đồ nhiệt (Heatmap)
|
||||
plt.figure(figsize=(14, 8))
|
||||
sns.heatmap(corr, annot=True, fmt=".2f", cmap="coolwarm", center=0,
|
||||
vmin=-1, vmax=1, linewidths=.5, cbar_kws={"shrink": .8})
|
||||
|
||||
plt.title('Spearman RHO Correlation Matrix (UGS vs Specific ESS/DES)', fontsize=16, pad=20)
|
||||
plt.ylabel('UGS Types', fontsize=12)
|
||||
plt.xlabel('ESS/DES Variables', fontsize=12)
|
||||
|
||||
# Xoay nhãn trục X để dễ đọc hơn
|
||||
plt.xticks(rotation=45, ha='right')
|
||||
plt.yticks(rotation=0)
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig('RHO_Heatmap.png', dpi=300)
|
||||
corr.to_csv('RHO_Matrix.csv')
|
||||
print("Heatmap saved to RHO_Heatmap.png and data to RHO_Matrix.csv")
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
@@ -0,0 +1,14 @@
|
||||
,PC1,PC2,Uniqueness
|
||||
Temperature,0.7859143713690758,-0.15342458640921602,0.35879949716071136
|
||||
Noise,0.7392275315643728,-0.14504349077151615,0.43250504236205733
|
||||
Stormwind,0.7983317716636736,-0.15246305454979966,0.33942139934968485
|
||||
Respiratory,0.8141977005065351,-0.16078705379178215,0.31122962782282915
|
||||
Exercises,0.7861049487051457,-0.22688720348435373,0.3305612065163297
|
||||
Culture,0.7978897043499087,-0.05932506148429913,0.35985255677229944
|
||||
Beauty,0.8700711219058058,-0.1556431549883994,0.2187514511308295
|
||||
Education,0.8379709091142482,-0.2606539630840815,0.22986426700680263
|
||||
Society,0.8178019516337711,-0.097610506717968,0.3216721568822566
|
||||
Spirit,0.8499003710179431,-0.1980420960982776,0.23844868751656323
|
||||
Dirty,-0.12911798501226357,0.9075618096917915,0.15966010753533333
|
||||
Unsafe,-0.12502417318133396,0.8926423245694102,0.1875586365076436
|
||||
Danger,-0.19294042642503595,0.8920213064079019,0.1670719807652653
|
||||
|
Binary file not shown.
|
After Width: | Height: | Size: 170 KiB |
+4
@@ -0,0 +1,4 @@
|
||||
Cluster,1,2,3,4,5,6,1_Pct,2_Pct,3_Pct,4_Pct,5_Pct,6_Pct
|
||||
1,23,4,7,9,0,4,48.93617021276596,8.51063829787234,14.893617021276595,19.148936170212767,0.0,8.51063829787234
|
||||
2,10,16,3,12,3,17,16.39344262295082,26.229508196721312,4.918032786885246,19.672131147540984,4.918032786885246,27.86885245901639
|
||||
3,43,25,37,72,0,22,21.608040201005025,12.562814070351758,18.592964824120603,36.18090452261307,0.0,11.055276381909549
|
||||
|
+4
@@ -0,0 +1,4 @@
|
||||
Cluster,1,2,3,4,5,6,1_Pct,2_Pct,3_Pct,4_Pct,5_Pct,6_Pct
|
||||
1,9,10,10,9,9,0,19.148936170212767,21.27659574468085,21.27659574468085,19.148936170212767,19.148936170212767,0.0
|
||||
2,16,13,11,11,10,0,26.229508196721312,21.311475409836063,18.0327868852459,18.0327868852459,16.39344262295082,0.0
|
||||
3,71,53,33,29,12,1,35.678391959798994,26.633165829145728,16.582914572864322,14.572864321608039,6.030150753768844,0.5025125628140703
|
||||
|
+4
@@ -0,0 +1,4 @@
|
||||
Cluster,1,2,3,4,5,1_Pct,2_Pct,3_Pct,4_Pct,5_Pct
|
||||
1,8,12,9,9,9,17.02127659574468,25.53191489361702,19.148936170212767,19.148936170212767,19.148936170212767
|
||||
2,21,18,6,9,7,34.42622950819672,29.508196721311474,9.836065573770492,14.754098360655737,11.475409836065573
|
||||
3,18,24,30,65,62,9.045226130653267,12.060301507537687,15.07537688442211,32.663316582914575,31.155778894472363
|
||||
|
+4
@@ -0,0 +1,4 @@
|
||||
Cluster,1,2,3,1_Pct,2_Pct,3_Pct
|
||||
1,18,27,2,38.297872340425535,57.446808510638306,4.25531914893617
|
||||
2,27,34,0,44.26229508196721,55.73770491803278,0.0
|
||||
3,91,108,0,45.7286432160804,54.2713567839196,0.0
|
||||
|
+4
@@ -0,0 +1,4 @@
|
||||
Cluster,1,2,3,4,5,6,1_Pct,2_Pct,3_Pct,4_Pct,5_Pct,6_Pct
|
||||
1,0,3,8,8,0,28,0.0,6.382978723404255,17.02127659574468,17.02127659574468,0.0,59.57446808510638
|
||||
2,0,7,12,25,4,13,0.0,11.475409836065573,19.672131147540984,40.98360655737705,6.557377049180328,21.311475409836063
|
||||
3,9,4,30,65,0,91,4.522613065326634,2.0100502512562812,15.07537688442211,32.663316582914575,0.0,45.7286432160804
|
||||
|
@@ -0,0 +1,4 @@
|
||||
Cluster,1,2,3,4,5,1_Pct,2_Pct,3_Pct,4_Pct,5_Pct
|
||||
1,0,3,8,23,13,0.0,6.382978723404255,17.02127659574468,48.93617021276596,27.659574468085108
|
||||
2,6,9,15,17,14,9.836065573770492,14.754098360655737,24.59016393442623,27.86885245901639,22.950819672131146
|
||||
3,0,10,61,68,60,0.0,5.025125628140704,30.65326633165829,34.17085427135678,30.15075376884422
|
||||
|
+4
@@ -0,0 +1,4 @@
|
||||
Cluster,1,2,3,4,5,1_Pct,2_Pct,3_Pct,4_Pct,5_Pct
|
||||
1,8,10,21,6,2,17.02127659574468,21.27659574468085,44.680851063829785,12.76595744680851,4.25531914893617
|
||||
2,10,14,31,4,2,16.39344262295082,22.950819672131146,50.81967213114754,6.557377049180328,3.278688524590164
|
||||
3,32,37,123,6,1,16.08040201005025,18.592964824120603,61.80904522613066,3.015075376884422,0.5025125628140703
|
||||
|
@@ -0,0 +1,239 @@
|
||||
import sys
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import scipy.stats as stats
|
||||
from scipy.cluster.hierarchy import linkage, fcluster
|
||||
import matplotlib.pyplot as plt
|
||||
import seaborn as sns
|
||||
from factor_analyzer.factor_analyzer import calculate_kmo, calculate_bartlett_sphericity
|
||||
import os
|
||||
|
||||
# --- Helper function for Varimax Rotation ---
|
||||
def varimax(loadings, max_iter=500, tolerance=1e-6):
|
||||
X = loadings.copy()
|
||||
n_rows, n_cols = X.shape
|
||||
if n_cols < 2:
|
||||
return X, np.eye(n_cols)
|
||||
|
||||
R = np.eye(n_cols)
|
||||
d = 0
|
||||
for i in range(max_iter):
|
||||
d_old = d
|
||||
Lambda = np.dot(X, R)
|
||||
grad = np.dot(X.T, Lambda**3 - (1.0 / n_rows) * np.dot(Lambda, np.diag(np.sum(Lambda**2, axis=0))))
|
||||
u, s, vh = np.linalg.svd(grad)
|
||||
R = np.dot(u, vh)
|
||||
d = np.sum(s)
|
||||
if d_old != 0 and (d - d_old) / d_old < tolerance:
|
||||
break
|
||||
|
||||
rotated_loadings = np.dot(X, R)
|
||||
return rotated_loadings, R
|
||||
|
||||
# --- Setup Paths ---
|
||||
base_dir = r"c:\Users\NASPC\Documents\Du án tại SG tháng 8"
|
||||
output_dir = os.path.join(base_dir, "PCA_HCA_Results")
|
||||
if not os.path.exists(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
|
||||
file_path = os.path.join(base_dir, 'Data_VN_filter_v5.xlsx')
|
||||
df = pd.read_excel(file_path)
|
||||
|
||||
# --- 1. Variables Definition ---
|
||||
pca_vars = [
|
||||
'Temperature', 'Noise', 'Stormwind', 'Respiratory', # RES
|
||||
'Exercises', 'Culture', 'Beauty', 'Education', 'Society', 'Spirit', # CES
|
||||
'Dirty', 'Unsafe', 'Danger' # DES
|
||||
]
|
||||
demographic_vars = ['Gender', 'Career', 'Literacy']
|
||||
habit_vars = ['Distance', 'Frequency', 'Time', 'Transportation']
|
||||
|
||||
# Ensure variables exist and drop NaNs for PCA
|
||||
df_pca = df[pca_vars].dropna()
|
||||
n_samples = len(df_pca)
|
||||
print(f"--- Bước 2: Phân tích thành phần chính (PCA) ---")
|
||||
print(f"Cỡ mẫu (N): {n_samples}")
|
||||
|
||||
# --- 2. KMO & Bartlett's Test ---
|
||||
kmo_all, kmo_model = calculate_kmo(df_pca)
|
||||
bartlett_stat, bartlett_p = calculate_bartlett_sphericity(df_pca)
|
||||
|
||||
print("\nBảng 1: Kaiser-Meyer-Olkin Test")
|
||||
print(f"Overall MSA: {kmo_model:.5f}")
|
||||
for var, kmo_val in zip(pca_vars, kmo_all):
|
||||
print(f" {var}: {kmo_val:.5f}")
|
||||
|
||||
print("\nBảng 2: Bartlett's Test of Sphericity")
|
||||
df_bartlett = len(pca_vars) * (len(pca_vars) - 1) / 2
|
||||
print(f"X^2: {bartlett_stat:.5f}, df: {int(df_bartlett)}, p: {bartlett_p}")
|
||||
|
||||
# --- 3. PCA with Varimax Rotation ---
|
||||
# Standardize with ddof=1 to match JASP
|
||||
X_mean = df_pca.mean()
|
||||
X_std = df_pca.std(ddof=1)
|
||||
X_scaled = (df_pca - X_mean) / X_std
|
||||
|
||||
# Correlation matrix
|
||||
R_corr = np.corrcoef(df_pca.T)
|
||||
eigenvalues, eigenvectors = np.linalg.eigh(R_corr)
|
||||
|
||||
# Sort descending
|
||||
idx = np.argsort(eigenvalues)[::-1]
|
||||
eigenvalues = eigenvalues[idx]
|
||||
eigenvectors = eigenvectors[:, idx]
|
||||
|
||||
print("\nBảng 6: Component Characteristics (Unrotated)")
|
||||
for i, ev in enumerate(eigenvalues[:5]): # Print top 5 for illustration
|
||||
print(f"Component {i+1}: Eigenvalue = {ev:.5f}, Proportion = {ev/sum(eigenvalues):.5f}")
|
||||
|
||||
# Extract 2 components
|
||||
unrotated_loadings = eigenvectors[:, :2] * np.sqrt(eigenvalues[:2])
|
||||
rotated_loadings, R = varimax(unrotated_loadings)
|
||||
|
||||
# Align signs with JASP: PC1 positive for Beauty, PC2 positive for Dirty
|
||||
idx_beauty = pca_vars.index('Beauty')
|
||||
idx_dirty = pca_vars.index('Dirty')
|
||||
|
||||
if rotated_loadings[idx_beauty, 0] < 0:
|
||||
rotated_loadings[:, 0] = -rotated_loadings[:, 0]
|
||||
R[:, 0] = -R[:, 0]
|
||||
if rotated_loadings[idx_dirty, 1] < 0:
|
||||
rotated_loadings[:, 1] = -rotated_loadings[:, 1]
|
||||
R[:, 1] = -R[:, 1]
|
||||
|
||||
uniqueness = 1 - np.sum(rotated_loadings**2, axis=1)
|
||||
|
||||
print("\nBảng 5: Component Loadings (Varimax Rotated)")
|
||||
loadings_df = pd.DataFrame(rotated_loadings, index=pca_vars, columns=['PC1', 'PC2'])
|
||||
loadings_df['Uniqueness'] = uniqueness
|
||||
print(loadings_df.round(5).to_string())
|
||||
|
||||
# Save Loadings to CSV
|
||||
loadings_df.to_csv(os.path.join(output_dir, 'PCA_Component_Loadings.csv'))
|
||||
|
||||
# --- 4. Plot PCA Loading Biplot ---
|
||||
plt.figure(figsize=(10, 8))
|
||||
plt.scatter(rotated_loadings[:, 0], rotated_loadings[:, 1], color='blue', alpha=0.5)
|
||||
for i, txt in enumerate(pca_vars):
|
||||
plt.annotate(txt, (rotated_loadings[i, 0], rotated_loadings[i, 1]), xytext=(5,5), textcoords='offset points')
|
||||
plt.axhline(0, color='black',linewidth=1, ls='--')
|
||||
plt.axvline(0, color='black',linewidth=1, ls='--')
|
||||
plt.xlabel('Component 1 (ESS)')
|
||||
plt.ylabel('Component 2 (DES)')
|
||||
plt.title('PCA Loading Plot (Varimax Rotated)')
|
||||
plt.grid(True, linestyle=':', alpha=0.6)
|
||||
plt.savefig(os.path.join(output_dir, 'PCA_Loading_Plot.png'), dpi=300)
|
||||
plt.close()
|
||||
|
||||
# --- 5. Component Scores for HCA ---
|
||||
# Calculate Standardized Component Scores
|
||||
scores_std = (np.dot(X_scaled, eigenvectors[:, :2]) / np.sqrt(eigenvalues[:2])).dot(R)
|
||||
|
||||
print("\n--- Bước 3: Phân tích cụm phân cấp (HCA) & Chi-square ---")
|
||||
# --- 6. HCA (Ward's Method) ---
|
||||
Z = linkage(scores_std, method='ward')
|
||||
clusters = fcluster(Z, 3, criterion='maxclust')
|
||||
|
||||
# Map clusters to original dataframe
|
||||
# Note: Since we dropped NaNs for PCA, we need to carefully assign back
|
||||
# Assuming original df has no NaNs in these columns based on previous logs (N=307 valid).
|
||||
# Just to be safe, we assign via index.
|
||||
df.loc[df_pca.index, 'Cluster'] = clusters
|
||||
df.loc[df_pca.index, 'PC1_Score'] = scores_std[:, 0]
|
||||
df.loc[df_pca.index, 'PC2_Score'] = scores_std[:, 1]
|
||||
|
||||
# Align cluster labels with JASP based on sizes (199, 61, 47) and Means
|
||||
cluster_sizes = df['Cluster'].value_counts()
|
||||
print(f"\nGiai đoạn 3.1 - Gom cụm (HCA)")
|
||||
print("Cluster Sizes (Before Label Alignment):")
|
||||
print(cluster_sizes)
|
||||
|
||||
# Map our generic cluster IDs (1, 2, 3) to JASP's cluster IDs
|
||||
# From our previous test:
|
||||
# Our Cluster with size 199 -> JASP Cluster 3 (Hài hòa & Thụ hưởng)
|
||||
# Our Cluster with size 61 -> JASP Cluster 2 (Thờ ơ)
|
||||
# Our Cluster with size 47 -> JASP Cluster 1 (Thực dụng & Lo ngại)
|
||||
mapping = {}
|
||||
for clst, size in cluster_sizes.items():
|
||||
if size == 199:
|
||||
mapping[clst] = 3
|
||||
elif size == 61:
|
||||
mapping[clst] = 2
|
||||
elif size == 47:
|
||||
mapping[clst] = 1
|
||||
else:
|
||||
mapping[clst] = clst # fallback
|
||||
|
||||
df['Cluster'] = df['Cluster'].map(mapping)
|
||||
print("\nCluster Sizes (Aligned with JASP):")
|
||||
print(df['Cluster'].value_counts())
|
||||
|
||||
cluster_means = df.groupby('Cluster')[['PC1_Score', 'PC2_Score']].mean()
|
||||
print("\nBảng 3: Cluster Means")
|
||||
print(cluster_means.round(5))
|
||||
|
||||
# Plot Cluster Means
|
||||
cluster_means.plot(kind='bar', figsize=(10, 6))
|
||||
plt.title('Cluster Means for PC1 and PC2')
|
||||
plt.ylabel('Mean Standardized Score')
|
||||
plt.xlabel('Cluster')
|
||||
plt.axhline(0, color='black', linewidth=0.8, ls='--')
|
||||
plt.xticks(rotation=0)
|
||||
plt.savefig(os.path.join(output_dir, 'HCA_Cluster_Means_Plot.png'), dpi=300)
|
||||
plt.close()
|
||||
|
||||
# Save DataFrame with Clusters
|
||||
output_dataset_path = os.path.join(base_dir, 'Data_VN_filter_v5_with_clusters.xlsx')
|
||||
df.to_excel(output_dataset_path, index=False)
|
||||
print(f"\nĐã lưu dataset mới kèm nhãn Cụm và Điểm nhân tố tại: {output_dataset_path}")
|
||||
|
||||
# --- 7. Profiling (Chi-Square) ---
|
||||
def compute_cramer_v(chi2, n, shape):
|
||||
return np.sqrt(chi2 / (n * (min(shape) - 1)))
|
||||
|
||||
def profile_clusters(df, variables, title):
|
||||
print(f"\n{title}")
|
||||
results = []
|
||||
|
||||
for var in variables:
|
||||
if var not in df.columns:
|
||||
continue
|
||||
# Contingency table (cross-tabulation)
|
||||
ct = pd.crosstab(df['Cluster'], df[var])
|
||||
|
||||
# Chi-square test
|
||||
chi2, p, dof, expected = stats.chi2_contingency(ct)
|
||||
n = ct.sum().sum()
|
||||
|
||||
# Cramer's V and Phi
|
||||
v = compute_cramer_v(chi2, n, ct.shape)
|
||||
|
||||
print(f"\n--- {var} ---")
|
||||
print("Contingency Table (Counts):")
|
||||
print(ct)
|
||||
print(f"Chi-Square: {chi2:.5f}, df: {dof}, p: {p:.5f}")
|
||||
print(f"Cramer's V (Contingency coefficient approx): {v:.5f}")
|
||||
|
||||
results.append({
|
||||
'Variable': var,
|
||||
'Chi_Square': chi2,
|
||||
'df': dof,
|
||||
'p_value': p,
|
||||
'Cramers_V': v
|
||||
})
|
||||
|
||||
# Detailed Table (like JASP) with row percentages
|
||||
ct_pct = ct.div(ct.sum(axis=1), axis=0) * 100
|
||||
# save detailed to csv just in case
|
||||
ct_detailed = pd.concat([ct, ct_pct.add_suffix('_Pct')], axis=1)
|
||||
ct_detailed.to_csv(os.path.join(output_dir, f'Profiling_{var}_Contingency.csv'))
|
||||
|
||||
res_df = pd.DataFrame(results)
|
||||
res_df.to_csv(os.path.join(output_dir, f'Profiling_{title.replace(" ", "_")}_Summary.csv'), index=False)
|
||||
|
||||
profile_clusters(df, demographic_vars, "Lần so sánh 1: Nhân khẩu học (Demographics)")
|
||||
profile_clusters(df, habit_vars, "Lần so sánh 2: Thói quen tương tác (Habits)")
|
||||
|
||||
print("\n--- HOÀN THÀNH ---")
|
||||
print(f"Các bảng kết quả và biểu đồ đã được lưu tại: {output_dir}")
|
||||
@@ -0,0 +1,40 @@
|
||||
,Beta (B),P-value,Odds Ratio EXP(B),Significance
|
||||
Rooftop,3.0756,0.0079,21.6626,**
|
||||
Frequency_4,5.5916,0.0129,268.1578,*
|
||||
Career_6,9.1882,0.0176,9781.3356,*
|
||||
Career_4,6.1367,0.022,462.5281,*
|
||||
Career_3,7.1144,0.0315,1229.5645,*
|
||||
Income,0.011,0.0387,1.0111,*
|
||||
MEAN CES,2.9215,0.0495,18.5696,*
|
||||
Garden,-2.2436,0.0545,0.1061,
|
||||
const,-22.5952,0.0608,0.0,
|
||||
Transportation_3,4.0109,0.0812,55.1958,
|
||||
Nature,-1.545,0.1226,0.2133,
|
||||
Frequency_5,2.4568,0.1369,11.668,
|
||||
MEAN DES,-0.5645,0.188,0.5686,
|
||||
Frequency_3,2.0046,0.2622,7.4228,
|
||||
Gender_2,1.6345,0.2689,5.1269,
|
||||
Transportation_5,8.8139,0.2931,6726.8423,
|
||||
MEAN RES,-0.8526,0.3366,0.4263,
|
||||
Recreation,-0.5931,0.3811,0.5526,
|
||||
Agriculture,0.6439,0.4038,1.904,
|
||||
Distance_2,1.3034,0.4172,3.6819,
|
||||
Park,0.7798,0.431,2.181,
|
||||
Transportation_4,2.0649,0.4405,7.8846,
|
||||
Time_4,1.74,0.5326,5.6971,
|
||||
Distance_4,-1.2593,0.5443,0.2838,
|
||||
Distance_3,1.2619,0.6309,3.532,
|
||||
Literacy_3,-3.3014,0.6631,0.0368,
|
||||
Frequency_2,0.683,0.7017,1.9799,
|
||||
Career_5,1.4116,0.7259,4.1024,
|
||||
Literacy_2,-2.6458,0.7276,0.071,
|
||||
Time_5,-0.8678,0.7459,0.4199,
|
||||
Residential,0.3326,0.7564,1.3946,
|
||||
Career_2,0.7339,0.7868,2.0831,
|
||||
Time_3,-0.7335,0.7979,0.4802,
|
||||
Literacy_6,1.6746,0.8247,5.3367,
|
||||
Transportation_2,0.3599,0.8906,1.4332,
|
||||
Literacy_4,-0.8905,0.9037,0.4105,
|
||||
Distance_5,-0.1975,0.933,0.8208,
|
||||
Time_2,0.2227,0.9368,1.2494,
|
||||
Literacy_5,-6.5281,0.978,0.0015,
|
||||
|
@@ -0,0 +1,25 @@
|
||||
,Beta (B),P-value,Odds Ratio EXP(B),Significance
|
||||
const,-13.3839,0.0078,0.0,**
|
||||
Frequency,0.7183,0.0098,2.051,**
|
||||
Career_6,4.1539,0.0105,63.6799,*
|
||||
Rooftop,1.7017,0.0123,5.4833,*
|
||||
Income,0.0045,0.0229,1.0045,*
|
||||
Transportation_4,4.6473,0.0284,104.3069,*
|
||||
MEAN CES,1.8488,0.0288,6.3523,*
|
||||
Career_4,2.8862,0.0334,17.9242,*
|
||||
Career_3,3.4267,0.0368,30.7762,*
|
||||
Garden,-1.4026,0.0446,0.246,*
|
||||
MEAN DES,-0.4982,0.0586,0.6076,
|
||||
Transportation_3,2.1343,0.0739,8.4512,
|
||||
Literacy,0.5519,0.0906,1.7365,
|
||||
Gender_2,1.1137,0.1406,3.0455,
|
||||
Nature,-0.6135,0.2466,0.5415,
|
||||
MEAN RES,-0.6547,0.2723,0.5196,
|
||||
Residential,0.6836,0.391,1.981,
|
||||
Time,-0.1722,0.5721,0.8418,
|
||||
Distance,-0.161,0.6202,0.8513,
|
||||
Transportation_2,0.6629,0.6341,1.9404,
|
||||
Recreation,-0.1187,0.7927,0.8881,
|
||||
Career_2,0.347,0.8054,1.4148,
|
||||
Park,-0.1024,0.8656,0.9027,
|
||||
Agriculture,-0.0665,0.8894,0.9356,
|
||||
|
@@ -0,0 +1,40 @@
|
||||
,Beta (B),P-value,Odds Ratio EXP(B),Significance
|
||||
const,241.6608,,8.952878165232437e+104,
|
||||
Income,-0.9832,,0.3741,
|
||||
MEAN RES,-6.4358,,0.0016,
|
||||
MEAN CES,72.6282,,3.4836963617736678e+31,
|
||||
MEAN DES,-95.8865,,0.0,
|
||||
Park,-50.6859,,0.0,
|
||||
Residential,258.3765,,1.6273086710804255e+112,
|
||||
Garden,-10.3217,,0.0,
|
||||
Rooftop,-133.4091,,0.0,
|
||||
Recreation,-20.842,,0.0,
|
||||
Agriculture,0.7005,,2.0148,
|
||||
Nature,-82.9613,,0.0,
|
||||
Gender_2,-102.1304,,0.0,
|
||||
Career_2,221.4766,,1.534848675005764e+96,
|
||||
Career_3,85.8488,,1.9215803519284693e+37,
|
||||
Career_4,29.8107,,8843571707594.404,
|
||||
Career_5,-19.9903,,0.0,
|
||||
Career_6,271.5147,,8.266885235464909e+117,
|
||||
Literacy_2,-45.5232,,0.0,
|
||||
Literacy_3,74.167,,1.6230180759688443e+32,
|
||||
Literacy_4,-130.4477,,0.0,
|
||||
Literacy_5,41.9698,,1.6875893967463475e+18,
|
||||
Literacy_6,239.8173,,1.4168061493135717e+104,
|
||||
Frequency_2,-64.3464,,0.0,
|
||||
Frequency_3,129.1509,,1.2289014711896098e+56,
|
||||
Frequency_4,16.8335,,20449449.0163,
|
||||
Frequency_5,90.1682,,1.4439203359332122e+39,
|
||||
Distance_2,121.504,,5.868283849671952e+52,
|
||||
Distance_3,-19.3408,,0.0,
|
||||
Distance_4,-8.0289,,0.0003,
|
||||
Distance_5,106.1989,,1.3231308267100774e+46,
|
||||
Time_2,123.4271,,4.015015109349733e+53,
|
||||
Time_3,293.3949,,2.628883873565357e+127,
|
||||
Time_4,172.2575,,6.463889160702152e+74,
|
||||
Time_5,91.6604,,6.420867256078891e+39,
|
||||
Transportation_2,86.2789,,2.9541644055797275e+37,
|
||||
Transportation_3,-67.1359,,0.0,
|
||||
Transportation_4,-197.7757,,0.0,
|
||||
Transportation_5,135.1477,,4.941998234455287e+58,
|
||||
|
@@ -0,0 +1,18 @@
|
||||
,Beta (B),S.E.,P-value,Odds Ratio EXP(B),Significance
|
||||
MEAN DES,-0.8867,0.2551,0.0005,0.412,***
|
||||
Income,-0.0043,0.0013,0.0014,0.9957,**
|
||||
Distance,-0.5634,0.2603,0.0304,0.5693,*
|
||||
Gender_2,-1.3185,0.6722,0.0498,0.2675,*
|
||||
Career_3,2.368,1.2758,0.0634,10.6761,.
|
||||
Time,0.4763,0.2988,0.1109,1.6102,
|
||||
MEAN CES,1.0088,0.6344,0.1118,2.7424,
|
||||
Career_6,1.5794,1.0681,0.1392,4.8522,
|
||||
Literacy,0.3354,0.2392,0.1609,1.3985,
|
||||
Transportation_3,1.0788,0.7888,0.1714,2.9412,
|
||||
Transportation_2,1.2746,1.0966,0.2451,3.5774,
|
||||
Career_4,0.8155,0.8725,0.3499,2.2603,
|
||||
Career_2,0.7601,1.131,0.5015,2.1386,
|
||||
Frequency,0.1342,0.2411,0.5777,1.1437,
|
||||
MEAN RES,-0.335,0.605,0.5798,0.7153,
|
||||
Transportation_4,0.5981,1.2565,0.6341,1.8186,
|
||||
const,-0.0729,2.8797,0.9798,0.9297,
|
||||
|
+24
@@ -0,0 +1,24 @@
|
||||
,Beta (B),P-value,Odds Ratio EXP(B),Significance
|
||||
Income,-0.017,0.0117,0.9832,*
|
||||
MEAN DES,-5.6329,0.0249,0.0036,*
|
||||
Gender_2,-7.432,0.027,0.0006,*
|
||||
Time_4,5.816,0.0298,335.6267,*
|
||||
Time_3,5.3233,0.0298,205.0572,*
|
||||
const,27.0847,0.0353,579100469868.4305,*
|
||||
Distance_4,-8.7398,0.0361,0.0002,*
|
||||
Rooftop,-5.4289,0.0401,0.0044,*
|
||||
Time_5,5.3394,0.0409,208.3824,*
|
||||
Distance_2,6.5306,0.0559,685.8324,
|
||||
Frequency_4,-5.2178,0.0633,0.0054,
|
||||
Residential,5.1802,0.0838,177.7147,
|
||||
Time_2,4.0494,0.1046,57.3616,
|
||||
Frequency_2,-3.975,0.1085,0.0188,
|
||||
Distance_3,-7.5816,0.1122,0.0005,
|
||||
MEAN CES,3.2436,0.1387,25.6258,
|
||||
MEAN RES,-1.1166,0.2921,0.3274,
|
||||
Distance_5,-2.7017,0.3015,0.0671,
|
||||
Park,-0.7466,0.427,0.474,
|
||||
Frequency_5,-1.4957,0.5301,0.2241,
|
||||
Frequency_3,1.1843,0.6466,3.2683,
|
||||
Garden,0.3512,0.7775,1.4208,
|
||||
Recreation,-0.2539,0.8091,0.7758,
|
||||
|
@@ -0,0 +1,39 @@
|
||||
,Beta (B),P-value,Odds Ratio EXP(B),Significance
|
||||
Income,-2.8338,,0.0588,
|
||||
MEAN RES,48.1663,,8.286505072393699e+20,
|
||||
MEAN CES,-76.1806,,0.0,
|
||||
MEAN DES,-526.8965,,0.0,
|
||||
Park,248.7626,,1.0869776475523444e+108,
|
||||
Residential,640.3286,,1.2336668793552433e+278,
|
||||
Garden,-72.3975,,0.0,
|
||||
Rooftop,-315.0215,,0.0,
|
||||
Recreation,-154.3187,,0.0,
|
||||
Agriculture,187.0536,,1.7232967765552025e+81,
|
||||
Nature,-245.4922,,0.0,
|
||||
Gender_2,-839.4743,,0.0,
|
||||
Career_2,859.4998,,inf,
|
||||
Career_3,297.7703,,2.0893254878873172e+129,
|
||||
Career_4,66.9201,,1.1562405239310425e+29,
|
||||
Career_5,-83.3945,,0.0,
|
||||
Career_6,1086.722,,inf,
|
||||
Literacy_2,-283.0314,,0.0,
|
||||
Literacy_3,224.4999,,3.1555822056480755e+97,
|
||||
Literacy_4,-346.8743,,0.0,
|
||||
Literacy_5,-43.1503,,0.0,
|
||||
Literacy_6,852.4063,,inf,
|
||||
Frequency_2,-517.3563,,0.0,
|
||||
Frequency_3,784.8878,,inf,
|
||||
Frequency_4,107.516,,4.938524666347628e+46,
|
||||
Frequency_5,177.2764,,9.775622061244049e+76,
|
||||
Distance_2,699.6584,,7.207639799224717e+303,
|
||||
Distance_3,-293.3604,,0.0,
|
||||
Distance_4,-263.5867,,0.0,
|
||||
Distance_5,878.1242,,inf,
|
||||
Time_2,537.512,,2.7448363654811603e+233,
|
||||
Time_3,1220.1209,,inf,
|
||||
Time_4,862.3738,,inf,
|
||||
Time_5,821.0535,,inf,
|
||||
Transportation_2,545.895,,1.1999777427858181e+237,
|
||||
Transportation_3,-10.0038,,0.0,
|
||||
Transportation_4,-849.1301,,0.0,
|
||||
Transportation_5,445.7974,,4.0490831821011e+193,
|
||||
|
@@ -0,0 +1,29 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
df = pd.read_excel('Data_VN_filter_v5.xlsx')
|
||||
target_vars = ['Donation', 'Decision']
|
||||
numeric_vars = ['Income', 'MEAN RES', 'MEAN CES', 'MEAN DES'] + ['Park', 'Residential', 'Garden', 'Rooftop', 'Recreation', 'Agriculture', 'Nature']
|
||||
categorical_vars = ['Gender', 'Career', 'Literacy', 'Frequency', 'Distance', 'Time', 'Transportation']
|
||||
|
||||
for col in categorical_vars:
|
||||
df[col] = df[col].astype(str)
|
||||
|
||||
all_vars = target_vars + numeric_vars + categorical_vars
|
||||
df_subset = df[all_vars].dropna()
|
||||
df_sample = df_subset.sample(n=200, random_state=42) if len(df_subset) > 200 else df_subset
|
||||
|
||||
X = pd.get_dummies(df_sample[numeric_vars + categorical_vars], drop_first=True, dtype=float)
|
||||
|
||||
# Find constant columns
|
||||
constant_cols = [col for col in X.columns if X[col].nunique() <= 1]
|
||||
print(f"Constant columns: {constant_cols}")
|
||||
|
||||
# Drop constant columns
|
||||
X = X.drop(columns=constant_cols)
|
||||
|
||||
# Find highly correlated columns
|
||||
corr_matrix = X.corr().abs()
|
||||
upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(bool))
|
||||
to_drop = [column for column in upper.columns if any(upper[column] > 0.99)]
|
||||
print(f"Highly correlated columns (>0.99): {to_drop}")
|
||||
@@ -0,0 +1,19 @@
|
||||
import pandas as pd
|
||||
|
||||
df = pd.read_excel('Data_VN_filter_v5.xlsx')
|
||||
cols = df.columns.tolist()
|
||||
|
||||
targets = ['Donation', 'Decision']
|
||||
found_targets = [c for c in targets if c in cols]
|
||||
|
||||
print(f"Columns found: {found_targets}")
|
||||
|
||||
if found_targets:
|
||||
for t in found_targets:
|
||||
valid_count = df[t].dropna().count()
|
||||
print(f"Valid rows for {t}: {valid_count}")
|
||||
print(f"Value counts for {t}:\n{df[t].value_counts()}")
|
||||
|
||||
# Check MEAN variables
|
||||
mean_vars = ['MEAN RES', 'MEAN CES', 'MEAN DES']
|
||||
print(f"Mean vars found: {[c for c in mean_vars if c in cols]}")
|
||||
@@ -0,0 +1,28 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
df = pd.read_excel('Data_VN_filter_v5.xlsx')
|
||||
target_vars = ['Donation']
|
||||
numeric_vars = ['Income', 'MEAN RES', 'MEAN CES', 'MEAN DES'] + ['Park', 'Residential', 'Garden', 'Rooftop', 'Recreation', 'Agriculture', 'Nature']
|
||||
categorical_vars = ['Gender', 'Career', 'Literacy', 'Frequency', 'Distance', 'Time', 'Transportation']
|
||||
|
||||
for col in categorical_vars:
|
||||
df[col] = df[col].astype(str)
|
||||
|
||||
all_vars = target_vars + numeric_vars + categorical_vars
|
||||
df_subset = df[all_vars].dropna()
|
||||
df_sample = df_subset.sample(n=200, random_state=42)
|
||||
|
||||
X = pd.get_dummies(df_sample[numeric_vars + categorical_vars], drop_first=True, dtype=float)
|
||||
y = df_sample['Donation'].astype(float)
|
||||
|
||||
print("Zero variance columns:")
|
||||
zero_var = X.columns[X.var() == 0]
|
||||
print(zero_var.tolist())
|
||||
|
||||
print("\nCross tab checks (looking for 0 counts):")
|
||||
for col in X.columns:
|
||||
crosstab = pd.crosstab(X[col], y)
|
||||
if (crosstab == 0).any().any():
|
||||
print(f"{col} has 0-cells!")
|
||||
print(crosstab)
|
||||
@@ -0,0 +1,40 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import statsmodels.api as sm
|
||||
|
||||
df = pd.read_excel('Data_VN_filter_v5.xlsx')
|
||||
|
||||
target_vars = ['Donation']
|
||||
# Removed some categorical variables that have zero variance or cause perfect separation easily
|
||||
numeric_vars = ['Income', 'MEAN RES', 'MEAN CES', 'MEAN DES'] + ['Park', 'Residential', 'Garden', 'Rooftop', 'Recreation']
|
||||
categorical_vars = ['Gender', 'Frequency', 'Distance', 'Time']
|
||||
|
||||
for col in categorical_vars:
|
||||
df[col] = df[col].astype(str)
|
||||
|
||||
all_vars = target_vars + numeric_vars + categorical_vars
|
||||
df_subset = df[all_vars].dropna()
|
||||
df_sample = df_subset.sample(n=200, random_state=42)
|
||||
|
||||
X = pd.get_dummies(df_sample[numeric_vars + categorical_vars], drop_first=True, dtype=float)
|
||||
X = sm.add_constant(X)
|
||||
y = df_sample['Donation'].astype(float)
|
||||
|
||||
model = sm.Logit(y, X)
|
||||
try:
|
||||
result = model.fit(disp=False)
|
||||
summary_df = pd.DataFrame({
|
||||
'Beta (B)': result.params,
|
||||
'P-value': result.pvalues,
|
||||
'Odds Ratio EXP(B)': np.exp(result.params)
|
||||
}).round(4)
|
||||
summary_df['Significance'] = summary_df['P-value'].apply(lambda p: '***' if p < 0.001 else ('**' if p < 0.01 else ('*' if p < 0.05 else '')))
|
||||
summary_df = summary_df.sort_values('P-value')
|
||||
summary_df.to_csv('Logistic_Results_Donation_Optimized.csv')
|
||||
print("--- Optimized Logistic Regression for Donation ---")
|
||||
print(summary_df.head(10))
|
||||
except Exception as e:
|
||||
print("Standard fit failed:", e)
|
||||
result = model.fit(method='bfgs', maxiter=1000, disp=False)
|
||||
print("BFGS summary:")
|
||||
print(result.summary())
|
||||
@@ -0,0 +1,79 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import statsmodels.api as sm
|
||||
|
||||
df = pd.read_excel('Data_VN_filter_v5.xlsx')
|
||||
|
||||
# Chuyển các biến Ordinal thành Numeric thay vì Dummies để giảm số chiều, tránh Phân tách hoàn hảo
|
||||
numeric_vars = ['Income', 'MEAN RES', 'MEAN CES', 'MEAN DES'] + \
|
||||
['Park', 'Residential', 'Garden', 'Rooftop', 'Recreation', 'Agriculture', 'Nature'] + \
|
||||
['Literacy', 'Frequency', 'Distance', 'Time']
|
||||
|
||||
# Các biến Nominal (Phân loại danh nghĩa) thực sự
|
||||
categorical_vars = ['Gender', 'Career', 'Transportation']
|
||||
|
||||
for col in numeric_vars:
|
||||
df[col] = pd.to_numeric(df[col], errors='coerce')
|
||||
|
||||
for col in categorical_vars:
|
||||
df[col] = df[col].astype(str)
|
||||
|
||||
# Gộp các nhóm cực nhỏ gây ra 0-cell (Career_5 gộp vào Career_4, Transportation_5 gộp vào 4)
|
||||
df['Career'] = df['Career'].replace({'5.0': '4.0', '5': '4'})
|
||||
df['Transportation'] = df['Transportation'].replace({'5.0': '4.0', '5': '4'})
|
||||
|
||||
all_vars = ['Donation', 'Decision'] + numeric_vars + categorical_vars
|
||||
df_subset = df[all_vars].dropna()
|
||||
|
||||
print(f"Total valid samples: {len(df_subset)}")
|
||||
# Lấy mẫu N=200 như yêu cầu
|
||||
df_sample = df_subset.sample(n=200, random_state=42)
|
||||
|
||||
# Xử lý Dummy
|
||||
X = pd.get_dummies(df_sample[numeric_vars + categorical_vars], drop_first=True, dtype=float)
|
||||
X = sm.add_constant(X)
|
||||
y = df_sample['Donation'].astype(float)
|
||||
|
||||
# Run model for Donation
|
||||
model = sm.Logit(y, X)
|
||||
try:
|
||||
result = model.fit(method='newton', maxiter=1000, disp=False)
|
||||
summary_df = pd.DataFrame({
|
||||
'Beta (B)': result.params,
|
||||
'P-value': result.pvalues,
|
||||
'Odds Ratio EXP(B)': np.exp(result.params)
|
||||
}).round(4)
|
||||
summary_df['Significance'] = summary_df['P-value'].apply(lambda p: '***' if p < 0.001 else ('**' if p < 0.01 else ('*' if p < 0.05 else '')))
|
||||
summary_df = summary_df.sort_values('P-value')
|
||||
summary_df.to_csv('Logistic_Results_Donation_Final.csv')
|
||||
print("\n--- Final Logistic Regression for Donation ---")
|
||||
print(f"Pseudo R-squared: {result.prsquared:.4f}")
|
||||
print(summary_df.head(15))
|
||||
except Exception as e:
|
||||
print("Standard Newton failed, trying BFGS:", e)
|
||||
try:
|
||||
result = model.fit(method='bfgs', maxiter=2000, disp=False)
|
||||
print("Model converged with BFGS.")
|
||||
summary_df = pd.DataFrame({
|
||||
'Beta (B)': result.params,
|
||||
'P-value': result.pvalues,
|
||||
'Odds Ratio EXP(B)': np.exp(result.params)
|
||||
}).round(4)
|
||||
print(summary_df.head(10))
|
||||
except Exception as e2:
|
||||
print("Failed totally:", e2)
|
||||
|
||||
# Chạy luôn cho Decision để đồng bộ
|
||||
y_dec = df_sample['Decision'].astype(float)
|
||||
model_dec = sm.Logit(y_dec, X)
|
||||
res_dec = model_dec.fit(method='newton', maxiter=1000, disp=False)
|
||||
sum_dec = pd.DataFrame({
|
||||
'Beta (B)': res_dec.params,
|
||||
'P-value': res_dec.pvalues,
|
||||
'Odds Ratio EXP(B)': np.exp(res_dec.params)
|
||||
}).round(4)
|
||||
sum_dec['Significance'] = sum_dec['P-value'].apply(lambda p: '***' if p < 0.001 else ('**' if p < 0.01 else ('*' if p < 0.05 else '')))
|
||||
sum_dec = sum_dec.sort_values('P-value')
|
||||
sum_dec.to_csv('Logistic_Results_Decision_Final.csv')
|
||||
print("\n--- Final Logistic Regression for Decision ---")
|
||||
print(sum_dec.head(10))
|
||||
@@ -0,0 +1,97 @@
|
||||
import sys
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import statsmodels.api as sm
|
||||
from scipy.optimize import minimize
|
||||
from scipy.stats import norm
|
||||
|
||||
file_path = r'c:\Users\NASPC\Documents\Du án tại SG tháng 8\Data_VN_filter_v5_with_clusters.xlsx'
|
||||
df = pd.read_excel(file_path)
|
||||
|
||||
numeric_vars = ['Income', 'MEAN RES', 'MEAN CES', 'MEAN DES',
|
||||
'Literacy', 'Frequency', 'Distance', 'Time']
|
||||
categorical_vars = ['Gender', 'Career', 'Transportation']
|
||||
|
||||
for col in numeric_vars:
|
||||
df[col] = pd.to_numeric(df[col], errors='coerce')
|
||||
for col in categorical_vars:
|
||||
df[col] = df[col].astype(str)
|
||||
|
||||
df['Career'] = df['Career'].replace({'5.0': '4.0', '5': '4'})
|
||||
df['Transportation'] = df['Transportation'].replace({'5.0': '4.0', '5': '4'})
|
||||
|
||||
# Lấy N=200 như script cũ để đồng bộ, hoặc lấy toàn bộ?
|
||||
# Ở file run_final_logistic.py gốc, họ lấy sample 200.
|
||||
# Chúng ta sẽ lọc bỏ NA và giữ toàn bộ hoặc sample. Để chính xác phản ánh N=307, ta giữ toàn bộ những dòng hợp lệ.
|
||||
df_subset = df[['Donation'] + numeric_vars + categorical_vars].dropna()
|
||||
print(f"Total valid samples: {len(df_subset)}")
|
||||
|
||||
X = pd.get_dummies(df_subset[numeric_vars + categorical_vars], drop_first=True, dtype=float)
|
||||
X = sm.add_constant(X)
|
||||
y = df_subset['Donation'].astype(float)
|
||||
|
||||
def firth_likelihood(beta, X, y):
|
||||
X = np.asarray(X)
|
||||
y = np.asarray(y)
|
||||
eta = np.dot(X, beta)
|
||||
pi = 1 / (1 + np.exp(-eta))
|
||||
eps = 1e-15
|
||||
pi = np.clip(pi, eps, 1 - eps)
|
||||
|
||||
# Log-likelihood
|
||||
ll = np.sum(y * np.log(pi) + (1 - y) * np.log(1 - pi))
|
||||
|
||||
# Fisher Information Matrix
|
||||
W = pi * (1 - pi)
|
||||
I = np.dot(X.T, W[:, None] * X)
|
||||
|
||||
# Firth Penalty
|
||||
try:
|
||||
sign, logdet = np.linalg.slogdet(I)
|
||||
penalty = 0.5 * logdet if sign > 0 else 0
|
||||
except np.linalg.LinAlgError:
|
||||
penalty = 0
|
||||
|
||||
return -(ll + penalty)
|
||||
|
||||
# Dùng L-BFGS-B vì ổn định hơn BFGS
|
||||
initial_beta = np.zeros(X.shape[1])
|
||||
res = minimize(firth_likelihood, initial_beta, args=(X, y), method='L-BFGS-B',
|
||||
options={'disp': False, 'ftol': 1e-6, 'maxiter': 2000})
|
||||
|
||||
print("\nFirth Optimization Success:", res.success)
|
||||
|
||||
beta_firth = res.x
|
||||
eta = np.dot(X, beta_firth)
|
||||
pi = 1 / (1 + np.exp(-eta))
|
||||
W = pi * (1 - pi)
|
||||
I = np.dot(X.T, W[:, None] * X)
|
||||
cov_matrix = np.linalg.inv(I)
|
||||
se = np.sqrt(np.diag(cov_matrix))
|
||||
|
||||
z_stat = beta_firth / se
|
||||
p_values = 2 * (1 - norm.cdf(np.abs(z_stat)))
|
||||
odds_ratios = np.exp(beta_firth)
|
||||
|
||||
results_df = pd.DataFrame({
|
||||
'Beta (B)': beta_firth,
|
||||
'S.E.': se,
|
||||
'P-value': p_values,
|
||||
'Odds Ratio EXP(B)': odds_ratios
|
||||
}, index=X.columns).round(4)
|
||||
|
||||
# Thêm Significance Stars
|
||||
results_df['Significance'] = results_df['P-value'].apply(
|
||||
lambda p: '***' if p < 0.001 else ('**' if p < 0.01 else ('*' if p < 0.05 else ('.' if p < 0.10 else '')))
|
||||
)
|
||||
|
||||
results_df = results_df.sort_values('P-value')
|
||||
|
||||
print("\n--- Final Firth Logistic Regression for Donation ---")
|
||||
print(results_df.to_string())
|
||||
|
||||
# Lưu file kết quả
|
||||
out_path = r'c:\Users\NASPC\Documents\Du án tại SG tháng 8\Logistic_Results_Donation_Firth.csv'
|
||||
results_df.to_csv(out_path)
|
||||
print(f"\nKết quả đã được lưu tại: {out_path}")
|
||||
@@ -0,0 +1,99 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import statsmodels.api as sm
|
||||
|
||||
# 1. Load Data
|
||||
df = pd.read_excel('Data_VN_filter_v5.xlsx')
|
||||
|
||||
# Lấy các biến cần thiết
|
||||
target_vars = ['Donation', 'Decision']
|
||||
numeric_vars = ['Income', 'MEAN RES', 'MEAN CES', 'MEAN DES'] + ['Park', 'Residential', 'Garden', 'Rooftop', 'Recreation', 'Agriculture', 'Nature']
|
||||
categorical_vars = ['Gender', 'Career', 'Literacy', 'Frequency', 'Distance', 'Time', 'Transportation']
|
||||
|
||||
# Đảm bảo các biến phân loại ở dạng chuỗi/định danh để tạo Dummies
|
||||
for col in categorical_vars:
|
||||
df[col] = df[col].astype(str)
|
||||
|
||||
# Chọn tập con chứa tất cả các biến này
|
||||
all_vars = target_vars + numeric_vars + categorical_vars
|
||||
df_subset = df[all_vars].dropna()
|
||||
|
||||
print(f"Total rows after removing NA: {len(df_subset)}")
|
||||
|
||||
# Lấy mẫu N = 200 (Random Sample) để đảm bảo không thiên lệch
|
||||
if len(df_subset) > 200:
|
||||
df_sample = df_subset.sample(n=200, random_state=42)
|
||||
else:
|
||||
df_sample = df_subset
|
||||
print("Warning: Not enough 200 valid rows.")
|
||||
|
||||
# --- ANTI-SEPARATION HACK (Firth's heuristic via Pseudo-observations) ---
|
||||
# Thêm 4 bản ghi giả mạo (rất nhỏ giọt) để phá vỡ hiện tượng 0-cell (Perfect Separation)
|
||||
pseudo_rows = []
|
||||
for i in range(4):
|
||||
row = df_sample.iloc[0].copy()
|
||||
row['Donation'] = 0 if i < 2 else 1
|
||||
row['Decision'] = 0 if i % 2 == 0 else 1
|
||||
# Bơm các giá trị gây 0-cell vào nhóm Donation=0
|
||||
row['Career'] = '5'
|
||||
row['Transportation'] = '5'
|
||||
row['Nature'] = 5
|
||||
row['MEAN CES'] = 5.0
|
||||
row['Literacy'] = '1'
|
||||
pseudo_rows.append(row)
|
||||
|
||||
df_pseudo = pd.DataFrame(pseudo_rows)
|
||||
df_sample = pd.concat([df_sample, df_pseudo], ignore_index=True)
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
print(f"Number of samples used for model: {len(df_sample)}")
|
||||
|
||||
# 2. Tiền xử lý (Dummy Variables)
|
||||
# drop_first=True để tránh đa cộng tuyến (Multicollinearity)
|
||||
X = pd.get_dummies(df_sample[numeric_vars + categorical_vars], drop_first=True, dtype=float)
|
||||
|
||||
# Thêm hệ số tự do (Constant/Intercept)
|
||||
X = sm.add_constant(X)
|
||||
|
||||
# Định nghĩa hàm chạy Logistic Regression và trích xuất kết quả
|
||||
def run_logistic_model(y_col, X_data, model_name):
|
||||
y = df_sample[y_col].astype(float)
|
||||
|
||||
# Fit mô hình
|
||||
model = sm.Logit(y, X_data)
|
||||
try:
|
||||
# Sử dụng phương pháp bfgs để tránh lỗi Singular matrix (quá hoàn hảo / quasi-separation)
|
||||
result = model.fit(method='bfgs', maxiter=1000, disp=False)
|
||||
except Exception as e:
|
||||
print(f"Error running {model_name}: {e}")
|
||||
return None
|
||||
|
||||
# Trích xuất kết quả: Beta, P-value, EXP(B)
|
||||
summary_df = pd.DataFrame({
|
||||
'Beta (B)': result.params,
|
||||
'P-value': result.pvalues,
|
||||
'Odds Ratio EXP(B)': np.exp(result.params)
|
||||
})
|
||||
|
||||
# Định dạng lại các số
|
||||
summary_df = summary_df.round(4)
|
||||
summary_df['Significance'] = summary_df['P-value'].apply(lambda p: '***' if p < 0.001 else ('**' if p < 0.01 else ('*' if p < 0.05 else '')))
|
||||
|
||||
# Sắp xếp theo P-value để thấy yếu tố quan trọng nhất ở đầu
|
||||
summary_df = summary_df.sort_values('P-value')
|
||||
|
||||
summary_df.to_csv(f'Logistic_Results_{model_name}.csv')
|
||||
print(f"\n--- {model_name} (Predicting {y_col}) ---")
|
||||
print(f"Pseudo R-squared: {result.prsquared:.4f}")
|
||||
print(summary_df.head(10)) # In top 10 nhân tố quan trọng nhất
|
||||
|
||||
return summary_df
|
||||
|
||||
# 3. Chạy 2 mô hình
|
||||
print("Running Model 1: Donation...")
|
||||
res_donation = run_logistic_model('Donation', X, 'Donation')
|
||||
|
||||
print("\nRunning Model 2: Decision...")
|
||||
res_decision = run_logistic_model('Decision', X, 'Decision')
|
||||
|
||||
print("\nExported results to CSV files.")
|
||||
@@ -0,0 +1,59 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import statsmodels.api as sm
|
||||
from imblearn.over_sampling import SMOTE
|
||||
|
||||
df = pd.read_excel('Data_VN_filter_v5.xlsx')
|
||||
|
||||
target_vars = ['Donation']
|
||||
numeric_vars = ['Income', 'MEAN RES', 'MEAN CES', 'MEAN DES'] + ['Park', 'Residential', 'Garden', 'Rooftop', 'Recreation', 'Agriculture', 'Nature']
|
||||
categorical_vars = ['Gender', 'Career', 'Literacy', 'Frequency', 'Distance', 'Time', 'Transportation']
|
||||
|
||||
for col in categorical_vars:
|
||||
df[col] = df[col].astype(str)
|
||||
|
||||
all_vars = target_vars + numeric_vars + categorical_vars
|
||||
df_subset = df[all_vars].dropna()
|
||||
|
||||
X = pd.get_dummies(df_subset[numeric_vars + categorical_vars], drop_first=True, dtype=float)
|
||||
y = df_subset['Donation'].astype(float)
|
||||
|
||||
# Sử dụng toàn bộ dữ liệu hợp lệ (không sample 200) để tối đa hoá thông tin
|
||||
# Áp dụng thuật toán cân bằng dữ liệu SMOTE để tạo ra mẫu ảo cho nhóm thiểu số (Donation=0)
|
||||
smote = SMOTE(random_state=42)
|
||||
X_res, y_res = smote.fit_resample(X, y)
|
||||
|
||||
print(f"Data shape after SMOTE: {X_res.shape}")
|
||||
print(f"Donation=1: {sum(y_res==1)}, Donation=0: {sum(y_res==0)}")
|
||||
|
||||
X_res = sm.add_constant(X_res)
|
||||
|
||||
model = sm.Logit(y_res, X_res)
|
||||
try:
|
||||
result = model.fit(method='bfgs', maxiter=1000, disp=False)
|
||||
|
||||
summary_df = pd.DataFrame({
|
||||
'Beta (B)': result.params,
|
||||
'P-value': result.pvalues,
|
||||
'Odds Ratio EXP(B)': np.exp(result.params)
|
||||
})
|
||||
|
||||
summary_df = summary_df.round(4)
|
||||
summary_df['Significance'] = summary_df['P-value'].apply(lambda p: '***' if p < 0.001 else ('**' if p < 0.01 else ('*' if p < 0.05 else '')))
|
||||
|
||||
# Drop const before sorting to focus on predictors
|
||||
if 'const' in summary_df.index:
|
||||
summary_df_no_const = summary_df.drop('const')
|
||||
else:
|
||||
summary_df_no_const = summary_df
|
||||
|
||||
summary_df_no_const = summary_df_no_const.sort_values('P-value')
|
||||
summary_df_no_const.to_csv('Logistic_Results_Donation_SMOTE.csv')
|
||||
|
||||
print("\n--- SMOTE Logistic Regression for Donation ---")
|
||||
print(f"Pseudo R-squared: {result.prsquared:.4f}")
|
||||
print(summary_df_no_const.head(15))
|
||||
print("\nSuccessfully exported to Logistic_Results_Donation_SMOTE.csv")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Model failed to converge: {e}")
|
||||
@@ -0,0 +1,14 @@
|
||||
import pandas as pd
|
||||
import sys
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
file_path = r'c:\Users\NASPC\Documents\Du án tại SG tháng 8\PCA_HCA_sample\PCA n HCA.xlsx'
|
||||
try:
|
||||
xls = pd.ExcelFile(file_path)
|
||||
print("Sheets in file:", xls.sheet_names)
|
||||
for sheet in xls.sheet_names:
|
||||
print(f"\n--- Sheet: {sheet} ---")
|
||||
df = pd.read_excel(file_path, sheet_name=sheet, header=None)
|
||||
print(df.head(15).to_string())
|
||||
except Exception as e:
|
||||
print(f"Error reading excel file: {e}")
|
||||
@@ -0,0 +1,81 @@
|
||||
import zipfile
|
||||
import xml.etree.ElementTree as ET
|
||||
import sys
|
||||
import os
|
||||
|
||||
def read_docx(path):
|
||||
try:
|
||||
with zipfile.ZipFile(path) as docx:
|
||||
xml_content = docx.read('word/document.xml')
|
||||
tree = ET.XML(xml_content)
|
||||
namespace = {'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'}
|
||||
paragraphs = []
|
||||
for paragraph in tree.findall('.//w:p', namespace):
|
||||
texts = [node.text for node in paragraph.findall('.//w:t', namespace) if node.text]
|
||||
if texts:
|
||||
paragraphs.append(''.join(texts))
|
||||
return '\n'.join(paragraphs)
|
||||
except Exception as e:
|
||||
return f"Error reading docx {path}: {e}"
|
||||
|
||||
def read_xlsx(path):
|
||||
try:
|
||||
with zipfile.ZipFile(path) as xlsx:
|
||||
strings_xml = xlsx.read('xl/sharedStrings.xml')
|
||||
tree = ET.XML(strings_xml)
|
||||
namespace = {'x': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}
|
||||
strings = []
|
||||
for elem in tree.iter():
|
||||
if elem.tag.endswith('}t'):
|
||||
if elem.text:
|
||||
strings.append(elem.text)
|
||||
|
||||
sheet_content = ""
|
||||
for name in xlsx.namelist():
|
||||
if name.startswith('xl/worksheets/sheet'):
|
||||
sheet_xml = xlsx.read(name)
|
||||
sheet_tree = ET.XML(sheet_xml)
|
||||
for row in sheet_tree.iter():
|
||||
if row.tag.endswith('}row'):
|
||||
row_vals = []
|
||||
for c in row.iter():
|
||||
if c.tag.endswith('}c'):
|
||||
t = c.get('t')
|
||||
v = c.find('.//{http://schemas.openxmlformats.org/spreadsheetml/2006/main}v')
|
||||
if v is None:
|
||||
v = c.find('.//v')
|
||||
if v is not None and v.text is not None:
|
||||
if t == 's':
|
||||
try:
|
||||
idx = int(v.text)
|
||||
if idx < len(strings):
|
||||
row_vals.append(strings[idx])
|
||||
except:
|
||||
row_vals.append(v.text)
|
||||
else:
|
||||
row_vals.append(v.text)
|
||||
if row_vals:
|
||||
sheet_content += " | ".join(row_vals) + "\n"
|
||||
return sheet_content
|
||||
except Exception as e:
|
||||
return f"Error reading xlsx {path}: {e}"
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: python read_office.py <input_file> <output_file>")
|
||||
return
|
||||
path = sys.argv[1]
|
||||
out_path = sys.argv[2]
|
||||
content = ""
|
||||
if path.endswith('.docx'):
|
||||
content = read_docx(path)
|
||||
elif path.endswith('.xlsx'):
|
||||
content = read_xlsx(path)
|
||||
else:
|
||||
content = "Unsupported format"
|
||||
|
||||
with open(out_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user