chuyển đổi cấu trúc

This commit is contained in:
Victor Phan
2026-07-08 15:11:34 +07:00
parent b041ccc11a
commit b22d327c5f
45 changed files with 201 additions and 4 deletions
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
1 Temperature Noise Stormwind Respiratory Exercises Culture Beauty Education Society Spirit Dirty Unsafe Danger
2 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
3 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
4 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
5 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
6 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
7 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
8 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
1 0 1
2 Temperature -0.15445511914940652 0.006990515901429791
3 Noise -0.07615894298740968 0.00700492282153885
4 Stormwind -0.1563914823055307 0.007878888704259901
5 Respiratory -0.14800873993854352 -0.011635761620414456
6 Exercises -0.16208617036601874 -0.0003806098068870642
7 Culture -0.04550439013316019 0.00881620114706321
8 Beauty -0.19796961667785237 -0.0012069882779817856
9 Education -0.17984311395007466 0.008345565782577814
10 Society -0.16523244764182207 -0.011179747262609863
11 Spirit -0.1938278638629291 -0.01479224672248243
12 Dirty 0.662990083180058 0.026367956173616355
13 Unsafe 0.6446258846820032 -0.0004123561994046179
14 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
1 0 1
2 Park -0.20737329331501148 -0.002436125971632047
3 Residential -0.2155834258211865 -0.011730849372120936
4 Garden -0.14244079709884974 -0.004395477787898166
5 Rooftop -0.15500847340894128 -0.003399988119557015
6 Recreation -0.0682883185805637 0.027918075759378963
7 Agriculture 0.5601149289877119 -0.005751109303690985
8 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
1 Temperature Noise Stormwind Respiratory Exercises Culture Beauty Education Society Spirit Dirty Unsafe Danger
2 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
3 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
4 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
5 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
6 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
7 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
8 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
1 PC1 PC2 Uniqueness
2 Temperature 0.7859143713690758 -0.15342458640921602 0.35879949716071136
3 Noise 0.7392275315643728 -0.14504349077151615 0.43250504236205733
4 Stormwind 0.7983317716636736 -0.15246305454979966 0.33942139934968485
5 Respiratory 0.8141977005065351 -0.16078705379178215 0.31122962782282915
6 Exercises 0.7861049487051457 -0.22688720348435373 0.3305612065163297
7 Culture 0.7978897043499087 -0.05932506148429913 0.35985255677229944
8 Beauty 0.8700711219058058 -0.1556431549883994 0.2187514511308295
9 Education 0.8379709091142482 -0.2606539630840815 0.22986426700680263
10 Society 0.8178019516337711 -0.097610506717968 0.3216721568822566
11 Spirit 0.8499003710179431 -0.1980420960982776 0.23844868751656323
12 Dirty -0.12911798501226357 0.9075618096917915 0.15966010753533333
13 Unsafe -0.12502417318133396 0.8926423245694102 0.1875586365076436
14 Danger -0.19294042642503595 0.8920213064079019 0.1670719807652653
Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

@@ -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
1 Cluster 1 2 3 4 5 6 1_Pct 2_Pct 3_Pct 4_Pct 5_Pct 6_Pct
2 1 23 4 7 9 0 4 48.93617021276596 8.51063829787234 14.893617021276595 19.148936170212767 0.0 8.51063829787234
3 2 10 16 3 12 3 17 16.39344262295082 26.229508196721312 4.918032786885246 19.672131147540984 4.918032786885246 27.86885245901639
4 3 43 25 37 72 0 22 21.608040201005025 12.562814070351758 18.592964824120603 36.18090452261307 0.0 11.055276381909549
@@ -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
1 Cluster 1 2 3 4 5 6 1_Pct 2_Pct 3_Pct 4_Pct 5_Pct 6_Pct
2 1 9 10 10 9 9 0 19.148936170212767 21.27659574468085 21.27659574468085 19.148936170212767 19.148936170212767 0.0
3 2 16 13 11 11 10 0 26.229508196721312 21.311475409836063 18.0327868852459 18.0327868852459 16.39344262295082 0.0
4 3 71 53 33 29 12 1 35.678391959798994 26.633165829145728 16.582914572864322 14.572864321608039 6.030150753768844 0.5025125628140703
@@ -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
1 Cluster 1 2 3 4 5 1_Pct 2_Pct 3_Pct 4_Pct 5_Pct
2 1 8 12 9 9 9 17.02127659574468 25.53191489361702 19.148936170212767 19.148936170212767 19.148936170212767
3 2 21 18 6 9 7 34.42622950819672 29.508196721311474 9.836065573770492 14.754098360655737 11.475409836065573
4 3 18 24 30 65 62 9.045226130653267 12.060301507537687 15.07537688442211 32.663316582914575 31.155778894472363
@@ -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
1 Cluster 1 2 3 1_Pct 2_Pct 3_Pct
2 1 18 27 2 38.297872340425535 57.446808510638306 4.25531914893617
3 2 27 34 0 44.26229508196721 55.73770491803278 0.0
4 3 91 108 0 45.7286432160804 54.2713567839196 0.0
@@ -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
1 Cluster 1 2 3 4 5 6 1_Pct 2_Pct 3_Pct 4_Pct 5_Pct 6_Pct
2 1 0 3 8 8 0 28 0.0 6.382978723404255 17.02127659574468 17.02127659574468 0.0 59.57446808510638
3 2 0 7 12 25 4 13 0.0 11.475409836065573 19.672131147540984 40.98360655737705 6.557377049180328 21.311475409836063
4 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
1 Cluster 1 2 3 4 5 1_Pct 2_Pct 3_Pct 4_Pct 5_Pct
2 1 0 3 8 23 13 0.0 6.382978723404255 17.02127659574468 48.93617021276596 27.659574468085108
3 2 6 9 15 17 14 9.836065573770492 14.754098360655737 24.59016393442623 27.86885245901639 22.950819672131146
4 3 0 10 61 68 60 0.0 5.025125628140704 30.65326633165829 34.17085427135678 30.15075376884422
@@ -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
1 Cluster 1 2 3 4 5 1_Pct 2_Pct 3_Pct 4_Pct 5_Pct
2 1 8 10 21 6 2 17.02127659574468 21.27659574468085 44.680851063829785 12.76595744680851 4.25531914893617
3 2 10 14 31 4 2 16.39344262295082 22.950819672131146 50.81967213114754 6.557377049180328 3.278688524590164
4 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,
1 Beta (B) P-value Odds Ratio EXP(B) Significance
2 Rooftop 3.0756 0.0079 21.6626 **
3 Frequency_4 5.5916 0.0129 268.1578 *
4 Career_6 9.1882 0.0176 9781.3356 *
5 Career_4 6.1367 0.022 462.5281 *
6 Career_3 7.1144 0.0315 1229.5645 *
7 Income 0.011 0.0387 1.0111 *
8 MEAN CES 2.9215 0.0495 18.5696 *
9 Garden -2.2436 0.0545 0.1061
10 const -22.5952 0.0608 0.0
11 Transportation_3 4.0109 0.0812 55.1958
12 Nature -1.545 0.1226 0.2133
13 Frequency_5 2.4568 0.1369 11.668
14 MEAN DES -0.5645 0.188 0.5686
15 Frequency_3 2.0046 0.2622 7.4228
16 Gender_2 1.6345 0.2689 5.1269
17 Transportation_5 8.8139 0.2931 6726.8423
18 MEAN RES -0.8526 0.3366 0.4263
19 Recreation -0.5931 0.3811 0.5526
20 Agriculture 0.6439 0.4038 1.904
21 Distance_2 1.3034 0.4172 3.6819
22 Park 0.7798 0.431 2.181
23 Transportation_4 2.0649 0.4405 7.8846
24 Time_4 1.74 0.5326 5.6971
25 Distance_4 -1.2593 0.5443 0.2838
26 Distance_3 1.2619 0.6309 3.532
27 Literacy_3 -3.3014 0.6631 0.0368
28 Frequency_2 0.683 0.7017 1.9799
29 Career_5 1.4116 0.7259 4.1024
30 Literacy_2 -2.6458 0.7276 0.071
31 Time_5 -0.8678 0.7459 0.4199
32 Residential 0.3326 0.7564 1.3946
33 Career_2 0.7339 0.7868 2.0831
34 Time_3 -0.7335 0.7979 0.4802
35 Literacy_6 1.6746 0.8247 5.3367
36 Transportation_2 0.3599 0.8906 1.4332
37 Literacy_4 -0.8905 0.9037 0.4105
38 Distance_5 -0.1975 0.933 0.8208
39 Time_2 0.2227 0.9368 1.2494
40 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,
1 Beta (B) P-value Odds Ratio EXP(B) Significance
2 const -13.3839 0.0078 0.0 **
3 Frequency 0.7183 0.0098 2.051 **
4 Career_6 4.1539 0.0105 63.6799 *
5 Rooftop 1.7017 0.0123 5.4833 *
6 Income 0.0045 0.0229 1.0045 *
7 Transportation_4 4.6473 0.0284 104.3069 *
8 MEAN CES 1.8488 0.0288 6.3523 *
9 Career_4 2.8862 0.0334 17.9242 *
10 Career_3 3.4267 0.0368 30.7762 *
11 Garden -1.4026 0.0446 0.246 *
12 MEAN DES -0.4982 0.0586 0.6076
13 Transportation_3 2.1343 0.0739 8.4512
14 Literacy 0.5519 0.0906 1.7365
15 Gender_2 1.1137 0.1406 3.0455
16 Nature -0.6135 0.2466 0.5415
17 MEAN RES -0.6547 0.2723 0.5196
18 Residential 0.6836 0.391 1.981
19 Time -0.1722 0.5721 0.8418
20 Distance -0.161 0.6202 0.8513
21 Transportation_2 0.6629 0.6341 1.9404
22 Recreation -0.1187 0.7927 0.8881
23 Career_2 0.347 0.8054 1.4148
24 Park -0.1024 0.8656 0.9027
25 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,
1 Beta (B) P-value Odds Ratio EXP(B) Significance
2 const 241.6608 8.952878165232437e+104
3 Income -0.9832 0.3741
4 MEAN RES -6.4358 0.0016
5 MEAN CES 72.6282 3.4836963617736678e+31
6 MEAN DES -95.8865 0.0
7 Park -50.6859 0.0
8 Residential 258.3765 1.6273086710804255e+112
9 Garden -10.3217 0.0
10 Rooftop -133.4091 0.0
11 Recreation -20.842 0.0
12 Agriculture 0.7005 2.0148
13 Nature -82.9613 0.0
14 Gender_2 -102.1304 0.0
15 Career_2 221.4766 1.534848675005764e+96
16 Career_3 85.8488 1.9215803519284693e+37
17 Career_4 29.8107 8843571707594.404
18 Career_5 -19.9903 0.0
19 Career_6 271.5147 8.266885235464909e+117
20 Literacy_2 -45.5232 0.0
21 Literacy_3 74.167 1.6230180759688443e+32
22 Literacy_4 -130.4477 0.0
23 Literacy_5 41.9698 1.6875893967463475e+18
24 Literacy_6 239.8173 1.4168061493135717e+104
25 Frequency_2 -64.3464 0.0
26 Frequency_3 129.1509 1.2289014711896098e+56
27 Frequency_4 16.8335 20449449.0163
28 Frequency_5 90.1682 1.4439203359332122e+39
29 Distance_2 121.504 5.868283849671952e+52
30 Distance_3 -19.3408 0.0
31 Distance_4 -8.0289 0.0003
32 Distance_5 106.1989 1.3231308267100774e+46
33 Time_2 123.4271 4.015015109349733e+53
34 Time_3 293.3949 2.628883873565357e+127
35 Time_4 172.2575 6.463889160702152e+74
36 Time_5 91.6604 6.420867256078891e+39
37 Transportation_2 86.2789 2.9541644055797275e+37
38 Transportation_3 -67.1359 0.0
39 Transportation_4 -197.7757 0.0
40 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,
1 Beta (B) S.E. P-value Odds Ratio EXP(B) Significance
2 MEAN DES -0.8867 0.2551 0.0005 0.412 ***
3 Income -0.0043 0.0013 0.0014 0.9957 **
4 Distance -0.5634 0.2603 0.0304 0.5693 *
5 Gender_2 -1.3185 0.6722 0.0498 0.2675 *
6 Career_3 2.368 1.2758 0.0634 10.6761 .
7 Time 0.4763 0.2988 0.1109 1.6102
8 MEAN CES 1.0088 0.6344 0.1118 2.7424
9 Career_6 1.5794 1.0681 0.1392 4.8522
10 Literacy 0.3354 0.2392 0.1609 1.3985
11 Transportation_3 1.0788 0.7888 0.1714 2.9412
12 Transportation_2 1.2746 1.0966 0.2451 3.5774
13 Career_4 0.8155 0.8725 0.3499 2.2603
14 Career_2 0.7601 1.131 0.5015 2.1386
15 Frequency 0.1342 0.2411 0.5777 1.1437
16 MEAN RES -0.335 0.605 0.5798 0.7153
17 Transportation_4 0.5981 1.2565 0.6341 1.8186
18 const -0.0729 2.8797 0.9798 0.9297
@@ -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,
1 Beta (B) P-value Odds Ratio EXP(B) Significance
2 Income -0.017 0.0117 0.9832 *
3 MEAN DES -5.6329 0.0249 0.0036 *
4 Gender_2 -7.432 0.027 0.0006 *
5 Time_4 5.816 0.0298 335.6267 *
6 Time_3 5.3233 0.0298 205.0572 *
7 const 27.0847 0.0353 579100469868.4305 *
8 Distance_4 -8.7398 0.0361 0.0002 *
9 Rooftop -5.4289 0.0401 0.0044 *
10 Time_5 5.3394 0.0409 208.3824 *
11 Distance_2 6.5306 0.0559 685.8324
12 Frequency_4 -5.2178 0.0633 0.0054
13 Residential 5.1802 0.0838 177.7147
14 Time_2 4.0494 0.1046 57.3616
15 Frequency_2 -3.975 0.1085 0.0188
16 Distance_3 -7.5816 0.1122 0.0005
17 MEAN CES 3.2436 0.1387 25.6258
18 MEAN RES -1.1166 0.2921 0.3274
19 Distance_5 -2.7017 0.3015 0.0671
20 Park -0.7466 0.427 0.474
21 Frequency_5 -1.4957 0.5301 0.2241
22 Frequency_3 1.1843 0.6466 3.2683
23 Garden 0.3512 0.7775 1.4208
24 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,
1 Beta (B) P-value Odds Ratio EXP(B) Significance
2 Income -2.8338 0.0588
3 MEAN RES 48.1663 8.286505072393699e+20
4 MEAN CES -76.1806 0.0
5 MEAN DES -526.8965 0.0
6 Park 248.7626 1.0869776475523444e+108
7 Residential 640.3286 1.2336668793552433e+278
8 Garden -72.3975 0.0
9 Rooftop -315.0215 0.0
10 Recreation -154.3187 0.0
11 Agriculture 187.0536 1.7232967765552025e+81
12 Nature -245.4922 0.0
13 Gender_2 -839.4743 0.0
14 Career_2 859.4998 inf
15 Career_3 297.7703 2.0893254878873172e+129
16 Career_4 66.9201 1.1562405239310425e+29
17 Career_5 -83.3945 0.0
18 Career_6 1086.722 inf
19 Literacy_2 -283.0314 0.0
20 Literacy_3 224.4999 3.1555822056480755e+97
21 Literacy_4 -346.8743 0.0
22 Literacy_5 -43.1503 0.0
23 Literacy_6 852.4063 inf
24 Frequency_2 -517.3563 0.0
25 Frequency_3 784.8878 inf
26 Frequency_4 107.516 4.938524666347628e+46
27 Frequency_5 177.2764 9.775622061244049e+76
28 Distance_2 699.6584 7.207639799224717e+303
29 Distance_3 -293.3604 0.0
30 Distance_4 -263.5867 0.0
31 Distance_5 878.1242 inf
32 Time_2 537.512 2.7448363654811603e+233
33 Time_3 1220.1209 inf
34 Time_4 862.3738 inf
35 Time_5 821.0535 inf
36 Transportation_2 545.895 1.1999777427858181e+237
37 Transportation_3 -10.0038 0.0
38 Transportation_4 -849.1301 0.0
39 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()