Spaces:
Sleeping
Sleeping
Update analyze.py
Browse files- analyze.py +182 -42
analyze.py
CHANGED
@@ -3,6 +3,7 @@ import pandas as pd
|
|
3 |
import plotly.express as px
|
4 |
import plotly.graph_objects as go
|
5 |
from plotly.subplots import make_subplots
|
|
|
6 |
|
7 |
# Hàm để tải và xử lý dữ liệu
|
8 |
@st.cache_data
|
@@ -65,11 +66,42 @@ def plot_criteria_comparison(df):
|
|
65 |
# Hàm tạo biểu đồ phân bố ứng viên theo khoảng điểm
|
66 |
def plot_score_range_distribution(df):
|
67 |
df['Khoảng điểm'] = pd.cut(df['Điểm tổng quát'], bins=[0, 2, 4, 6, 8, 10], labels=['0-2', '2-4', '4-6', '6-8', '8-10'])
|
68 |
-
|
|
|
69 |
fig.update_xaxes(title="Khoảng điểm")
|
70 |
fig.update_yaxes(title="Số lượng ứng viên")
|
71 |
return fig
|
72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
def dashboard():
|
74 |
# Tiêu đề ứng dụng
|
75 |
st.header("📈 Dashboard Phân tích Ứng viên")
|
@@ -81,63 +113,171 @@ def dashboard():
|
|
81 |
# Tải dữ liệu
|
82 |
df = load_data(uploaded_file)
|
83 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
84 |
# Thông tin tổng quan
|
85 |
st.header("📊 Thông tin tổng quan")
|
86 |
col1, col2, col3, col4 = st.columns(4)
|
87 |
col1.metric("Tổng số ứng viên", len(df))
|
88 |
-
col2.metric("Điểm trung bình", f"{df['Điểm tổng quát'].mean():.2f}")
|
89 |
-
col3.metric("Điểm cao nhất", df['Điểm tổng quát'].max())
|
90 |
-
col4.metric("Điểm thấp nhất", df['Điểm tổng quát'].min())
|
91 |
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
|
|
99 |
|
100 |
-
# Phân tích
|
101 |
-
|
102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
103 |
|
104 |
-
# Phân
|
105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
106 |
|
107 |
# Biểu đồ radar cho từng ứng viên
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
|
|
|
|
|
|
|
|
|
|
117 |
|
118 |
# Lọc và sắp xếp ứng viên
|
119 |
st.header("🔍 Lọc và sắp xếp ứng viên")
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
131 |
|
132 |
# Top ứng viên
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
|
|
137 |
|
138 |
# Dữ liệu chi tiết
|
139 |
-
st.
|
140 |
-
|
141 |
|
142 |
else:
|
143 |
st.info("Vui lòng upload file CSV để bắt đầu phân tích.")
|
|
|
3 |
import plotly.express as px
|
4 |
import plotly.graph_objects as go
|
5 |
from plotly.subplots import make_subplots
|
6 |
+
import numpy as np
|
7 |
|
8 |
# Hàm để tải và xử lý dữ liệu
|
9 |
@st.cache_data
|
|
|
66 |
# Hàm tạo biểu đồ phân bố ứng viên theo khoảng điểm
|
67 |
def plot_score_range_distribution(df):
|
68 |
df['Khoảng điểm'] = pd.cut(df['Điểm tổng quát'], bins=[0, 2, 4, 6, 8, 10], labels=['0-2', '2-4', '4-6', '6-8', '8-10'])
|
69 |
+
counts = df['Khoảng điểm'].value_counts().sort_index()
|
70 |
+
fig = px.bar(counts, title="Phân bố ứng viên theo khoảng điểm")
|
71 |
fig.update_xaxes(title="Khoảng điểm")
|
72 |
fig.update_yaxes(title="Số lượng ứng viên")
|
73 |
return fig
|
74 |
|
75 |
+
# Hàm tạo biểu đồ phân bố ứng viên theo giai đoạn
|
76 |
+
def plot_stage_distribution(df):
|
77 |
+
if 'Giai đoạn' in df.columns:
|
78 |
+
fig = px.bar(df['Giai đoạn'].value_counts().sort_index(),
|
79 |
+
title="Phân bố ứng viên theo giai đoạn tuyển dụng")
|
80 |
+
fig.update_xaxes(title="Giai đoạn")
|
81 |
+
fig.update_yaxes(title="Số lượng ứng viên")
|
82 |
+
return fig
|
83 |
+
return None
|
84 |
+
|
85 |
+
# Hàm tạo biểu đồ điểm trung bình theo giai đoạn
|
86 |
+
def plot_avg_score_by_stage(df):
|
87 |
+
if 'Giai đoạn' in df.columns:
|
88 |
+
avg_scores = df.groupby('Giai đoạn')['Điểm tổng quát'].mean().sort_values(ascending=False)
|
89 |
+
fig = px.bar(avg_scores, title="Điểm trung bình theo giai đoạn tuyển dụng")
|
90 |
+
fig.update_xaxes(title="Giai đoạn")
|
91 |
+
fig.update_yaxes(title="Điểm trung bình")
|
92 |
+
return fig
|
93 |
+
return None
|
94 |
+
|
95 |
+
# Hàm tạo biểu đồ heatmap so sánh điểm trung bình các tiêu chí theo giai đoạn
|
96 |
+
def plot_criteria_by_stage_heatmap(df):
|
97 |
+
if 'Giai đoạn' in df.columns:
|
98 |
+
criteria = ['Mức độ phù hợp', 'Kỹ năng kỹ thuật', 'Kinh nghiệm', 'Trình độ học vấn', 'Kỹ năng mềm']
|
99 |
+
pivot_df = df.pivot_table(index='Giai đoạn', values=criteria)
|
100 |
+
fig = px.imshow(pivot_df, text_auto=True, aspect="auto", color_continuous_scale="Blues",
|
101 |
+
title="Điểm trung bình các tiêu chí theo giai đoạn")
|
102 |
+
return fig
|
103 |
+
return None
|
104 |
+
|
105 |
def dashboard():
|
106 |
# Tiêu đề ứng dụng
|
107 |
st.header("📈 Dashboard Phân tích Ứng viên")
|
|
|
113 |
# Tải dữ liệu
|
114 |
df = load_data(uploaded_file)
|
115 |
|
116 |
+
# Kiểm tra và chuyển đổi các cột số nếu cần
|
117 |
+
numeric_columns = ['Mức độ phù hợp', 'Kỹ năng kỹ thuật', 'Kinh nghiệm',
|
118 |
+
'Trình độ học vấn', 'Kỹ năng mềm', 'Điểm tổng quát']
|
119 |
+
|
120 |
+
for col in numeric_columns:
|
121 |
+
if col in df.columns:
|
122 |
+
df[col] = pd.to_numeric(df[col], errors='coerce')
|
123 |
+
|
124 |
# Thông tin tổng quan
|
125 |
st.header("📊 Thông tin tổng quan")
|
126 |
col1, col2, col3, col4 = st.columns(4)
|
127 |
col1.metric("Tổng số ứng viên", len(df))
|
|
|
|
|
|
|
128 |
|
129 |
+
if 'Điểm tổng quát' in df.columns:
|
130 |
+
avg_score = df['Điểm tổng quát'].mean()
|
131 |
+
max_score = df['Điểm tổng quát'].max()
|
132 |
+
min_score = df['Điểm tổng quát'].min()
|
133 |
+
|
134 |
+
col2.metric("Điểm trung bình", f"{avg_score:.2f}")
|
135 |
+
col3.metric("Điểm cao nhất", f"{max_score:.2f}")
|
136 |
+
col4.metric("Điểm thấp nhất", f"{min_score:.2f}")
|
137 |
|
138 |
+
# Phân tích theo giai đoạn (mới)
|
139 |
+
if 'Giai đoạn' in df.columns:
|
140 |
+
st.header("🔄 Phân tích theo giai đoạn tuyển dụng")
|
141 |
+
col1, col2 = st.columns(2)
|
142 |
+
|
143 |
+
with col1:
|
144 |
+
stage_chart = plot_stage_distribution(df)
|
145 |
+
if stage_chart:
|
146 |
+
st.plotly_chart(stage_chart, use_container_width=True)
|
147 |
+
|
148 |
+
with col2:
|
149 |
+
avg_score_chart = plot_avg_score_by_stage(df)
|
150 |
+
if avg_score_chart:
|
151 |
+
st.plotly_chart(avg_score_chart, use_container_width=True)
|
152 |
+
|
153 |
+
# Biểu đồ heatmap các tiêu chí theo giai đoạn
|
154 |
+
criteria_stage_chart = plot_criteria_by_stage_heatmap(df)
|
155 |
+
if criteria_stage_chart:
|
156 |
+
st.plotly_chart(criteria_stage_chart, use_container_width=True)
|
157 |
|
158 |
+
# Phân phối điểm và Ma trận tương quan
|
159 |
+
if 'Điểm tổng quát' in df.columns:
|
160 |
+
st.header("📈 Phân tích điểm số")
|
161 |
+
col1, col2 = st.columns(2)
|
162 |
+
with col1:
|
163 |
+
st.plotly_chart(plot_score_distribution(df, 'Điểm tổng quát'), use_container_width=True)
|
164 |
+
with col2:
|
165 |
+
try:
|
166 |
+
st.plotly_chart(plot_correlation_heatmap(df), use_container_width=True)
|
167 |
+
except Exception as e:
|
168 |
+
st.warning(f"Không thể tạo ma trận tương quan: {e}")
|
169 |
+
|
170 |
+
# Phân tích đánh giá theo tiêu chí
|
171 |
+
st.header("🔬 Phân tích đánh giá theo tiêu chí")
|
172 |
+
try:
|
173 |
+
st.plotly_chart(plot_criteria_comparison(df), use_container_width=True)
|
174 |
+
except Exception as e:
|
175 |
+
st.warning(f"Không thể tạo biểu đồ so sánh tiêu chí: {e}")
|
176 |
+
|
177 |
+
# Phân bố ứng viên theo khoảng điểm
|
178 |
+
try:
|
179 |
+
st.plotly_chart(plot_score_range_distribution(df), use_container_width=True)
|
180 |
+
except Exception as e:
|
181 |
+
st.warning(f"Không thể tạo biểu đồ phân bố khoảng điểm: {e}")
|
182 |
|
183 |
# Biểu đồ radar cho từng ứng viên
|
184 |
+
if 'Tên ứng viên' in df.columns:
|
185 |
+
st.header("🎯 Biểu đồ kỹ năng ứng viên")
|
186 |
+
col1, col2 = st.columns([1, 3])
|
187 |
+
with col1:
|
188 |
+
selected_candidate = st.selectbox("Chọn ứng viên", df['Tên ứng viên'].tolist())
|
189 |
+
if 'Tóm tắt' in df.columns:
|
190 |
+
candidate_summary = df[df['Tên ứng viên'] == selected_candidate]['Tóm tắt'].values[0]
|
191 |
+
st.subheader("Tóm tắt ứng viên")
|
192 |
+
st.write(candidate_summary)
|
193 |
+
with col2:
|
194 |
+
try:
|
195 |
+
st.plotly_chart(plot_candidate_radar(df, selected_candidate), use_container_width=True)
|
196 |
+
except Exception as e:
|
197 |
+
st.warning(f"Không thể tạo biểu đồ radar: {e}")
|
198 |
|
199 |
# Lọc và sắp xếp ứng viên
|
200 |
st.header("🔍 Lọc và sắp xếp ứng viên")
|
201 |
+
|
202 |
+
# Cấu hình các bộ lọc
|
203 |
+
filter_columns = st.columns(3)
|
204 |
+
|
205 |
+
with filter_columns[0]:
|
206 |
+
# Tùy chọn lọc theo điểm
|
207 |
+
if 'Điểm tổng quát' in df.columns:
|
208 |
+
min_score = st.slider("Điểm tổng quát tối thiểu",
|
209 |
+
float(df['Điểm tổng quát'].min()),
|
210 |
+
float(df['Điểm tổng quát'].max()),
|
211 |
+
float(df['Điểm tổng quát'].min()))
|
212 |
+
else:
|
213 |
+
min_score = 0
|
214 |
+
|
215 |
+
with filter_columns[1]:
|
216 |
+
# Sắp xếp theo
|
217 |
+
sort_options = [col for col in df.columns if col in
|
218 |
+
['Điểm tổng quát', 'Mức độ phù hợp', 'Kỹ năng kỹ thuật',
|
219 |
+
'Kinh nghiệm', 'Trình độ học vấn', 'Kỹ năng mềm']]
|
220 |
+
|
221 |
+
if sort_options:
|
222 |
+
sort_by = st.selectbox("Sắp xếp theo", sort_options)
|
223 |
+
else:
|
224 |
+
sort_by = None
|
225 |
+
|
226 |
+
with filter_columns[2]:
|
227 |
+
# Lọc theo giai đoạn
|
228 |
+
if 'Giai đoạn' in df.columns and not df['Giai đoạn'].isna().all():
|
229 |
+
stages = ['Tất cả'] + list(df['Giai đoạn'].dropna().unique())
|
230 |
+
selected_stage = st.selectbox("Giai đoạn", stages)
|
231 |
+
else:
|
232 |
+
selected_stage = 'Tất cả'
|
233 |
+
|
234 |
+
# Áp dụng các bộ lọc
|
235 |
+
filtered_df = df.copy()
|
236 |
+
|
237 |
+
if 'Điểm tổng quát' in filtered_df.columns:
|
238 |
+
filtered_df = filtered_df[filtered_df['Điểm tổng quát'] >= min_score]
|
239 |
+
|
240 |
+
if selected_stage != 'Tất cả' and 'Giai đoạn' in filtered_df.columns:
|
241 |
+
filtered_df = filtered_df[filtered_df['Giai đoạn'] == selected_stage]
|
242 |
+
|
243 |
+
if sort_by and sort_by in filtered_df.columns:
|
244 |
+
filtered_df = filtered_df.sort_values(sort_by, ascending=False)
|
245 |
+
|
246 |
+
# Hiển thị dữ liệu đã lọc
|
247 |
+
display_columns = ['Tên ứng viên']
|
248 |
+
if 'Điểm tổng quát' in df.columns:
|
249 |
+
display_columns.append('Điểm tổng quát')
|
250 |
+
|
251 |
+
score_columns = [col for col in df.columns if col in
|
252 |
+
['Mức độ phù hợp', 'Kỹ năng kỹ thuật', 'Kinh nghiệm',
|
253 |
+
'Trình độ học vấn', 'Kỹ năng mềm']]
|
254 |
+
display_columns.extend(score_columns)
|
255 |
+
|
256 |
+
if 'Giai đoạn' in df.columns:
|
257 |
+
display_columns.append('Giai đoạn')
|
258 |
+
|
259 |
+
if 'Link ứng viên' in df.columns:
|
260 |
+
display_columns.append('Link ứng viên')
|
261 |
+
|
262 |
+
# Chỉ hiển thị các cột có trong DataFrame
|
263 |
+
display_columns = [col for col in display_columns if col in filtered_df.columns]
|
264 |
+
|
265 |
+
column_config = {}
|
266 |
+
if 'Link ứng viên' in display_columns:
|
267 |
+
column_config["Link ứng viên"] = st.column_config.LinkColumn()
|
268 |
+
|
269 |
+
st.dataframe(filtered_df[display_columns], column_config=column_config)
|
270 |
|
271 |
# Top ứng viên
|
272 |
+
if 'Điểm tổng quát' in df.columns:
|
273 |
+
st.header("🏆 Top ứng viên theo điểm tổng quát")
|
274 |
+
top_n = st.slider("Số lượng ứng viên hàng đầu", 1, min(20, len(df)), 5)
|
275 |
+
top_candidates = df.sort_values('Điểm tổng quát', ascending=False).head(top_n)
|
276 |
+
st.table(top_candidates[display_columns])
|
277 |
|
278 |
# Dữ liệu chi tiết
|
279 |
+
with st.expander("📋 Xem tất cả dữ liệu chi tiết"):
|
280 |
+
st.dataframe(df)
|
281 |
|
282 |
else:
|
283 |
st.info("Vui lòng upload file CSV để bắt đầu phân tích.")
|