display_dataframe_to_user

时间: 2024-09-20 17:11:02 AIGC 浏览: 439
`display_dataframe_to_user`通常是一个函数或方法名,它用于将数据框(DataFrame)的内容展示给用户,常出现在数据分析或数据可视化环境中,比如Python的Pandas库或者一些基于Web的数据分析工具。这个函数的作用是将DataFrame转换成用户友好的格式,如表格或图表形式,以便于理解和解读数据。 举个例子,在Jupyter Notebook或者某些数据处理库中,你可能会看到这样的代码: ```python import pandas as pd def display_dataframe_to_user(df): print(df.head()) # 显示前几行 df.to_html('output.html', index=False) # 将DataFrame保存为HTML文件供网页查看 # 使用示例 data = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]}) display_dataframe_to_user(data) ``` 在这个例子中,`display_dataframe_to_user`展示了数据框的首五行,并将其转化为HTML格式显示。
相关问题

--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) Cell In[1], line 3 1 import matplotlib.pyplot as plt 2 import scipy.stats as stats ----> 3 from ace_tools import display_dataframe_to_user ModuleNotFoundError: No module named 'ace_tools'

### 解决 `ModuleNotFoundError` 问题并安装或使用 `ace_tools` 库 在 Python 环境中遇到 `ModuleNotFoundError` 通常是因为所需的库未正确安装或环境配置不正确。以下是针对 `ace_tools` 库的解决方案: #### 1. 检查当前 Python 环境 确保已激活正确的 Python 环境。如果使用 Conda,可以通过以下命令激活环境: ```bash conda activate your_env_name ``` 检查当前环境中是否已安装 `ace_tools`: ```bash pip list | grep ace_tools ``` 如果未安装,则需要进行安装[^2]。 #### 2. 安装 `ace_tools` 库 尝试通过 `pip` 安装 `ace_tools`: ```bash pip install ace-tools ``` 如果上述命令失败,可能是因为该库未在 PyPI 上发布。此时可以考虑从源代码安装。例如,如果 `ace_tools` 的源代码托管在 GitHub 上,可以通过以下方式安装: ```bash git clone https://github.com/username/ace_tools.git cd ace_tools pip install . ``` #### 3. 验证安装 安装完成后,在 Python 脚本或交互式解释器中验证是否可以成功导入 `ace_tools`: ```python import ace_tools from ace_tools import display_dataframe_to_user ``` 如果仍然出现 `ModuleNotFoundError`,可能是安装路径未被添加到系统的 `PYTHONPATH` 中。可以通过以下方式检查安装路径: ```bash python -c "import site; print(site.getsitepackages())" ``` 确保 `ace_tools` 已安装到列出的路径之一。 #### 4. 处理 TensorFlow 兼容性问题 如果 `ace_tools` 依赖于特定版本的 TensorFlow(如 TensorFlow 2.5),需确保当前环境中安装的 TensorFlow 版本与之兼容。可以通过以下命令安装指定版本的 TensorFlow: ```bash pip install tensorflow==2.5 ``` 注意:如果使用的是 Python 3.7 或更高版本,建议选择支持的 TensorFlow 版本以避免兼容性问题[^1]。 #### 5. 配置环境变量 如果 `ace_tools` 需要额外的环境变量支持,可以参考类似配置方法[^3]。例如,将相关路径添加到 `PYTHONPATH`: ```bash export PYTHONPATH=/path/to/ace_tools:$PYTHONPATH ``` 确保此配置已生效,可通过以下命令验证: ```bash echo $PYTHONPATH ``` #### 示例代码 以下是一个完整的示例,展示如何在脚本中使用 `ace_tools`: ```python import ace_tools from ace_tools import display_dataframe_to_user # 示例数据 data = { 'Column1': [1, 2, 3], 'Column2': ['A', 'B', 'C'] } # 显示数据框 display_dataframe_to_user(data) ``` ---

--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) Cell In[1], line 30 24 result = pd.DataFrame({ 25 '充电频次': charging_frequency, 26 '充电时长(小时)': charging_duration 27 }).fillna(0) 29 # 展示结果 ---> 30 import ace_tools as tools; tools.display_dataframe_to_user(name="1-9月充电频次与充电时长统计", dataframe=result) ModuleNotFoundError: No module named 'ace_tools'

<think>嗯,用户遇到了一个ModuleNotFoundError,说找不到'ace_tools'模块。这个问题在Python中挺常见的,通常是模块没有安装或者名字拼写错误。首先,我需要确认这个错误的具体情况。 用户提供的错误信息显示,错误发生在第30行,尝试导入ace_tools时失败。那首先应该检查这个模块是否存在。ace_tools听起来像是一个第三方库或者可能是用户自己编写的工具模块。如果是第三方库,可能需要用pip安装。如果是自定义模块,可能需要确认文件路径是否正确。 接下来,我应该考虑可能的解决方案。第一步是让用户确认是否安装了ace_tools。可以通过运行pip list来查看已安装的包,或者直接尝试用pip install ace_tools安装。如果安装失败,说明这个包可能不在PyPI上,这时候需要进一步的信息。 另外,也有可能模块名称拼写错误,或者安装的环境不对。比如,用户是否在正确的Python环境中安装了这个包?有时候,特别是使用虚拟环境或者多个Python版本时,可能会出现这种情况。这时候需要确认安装路径和当前运行环境是否一致。 还有一种可能是,ace_tools是项目内部的模块,用户没有将其放在正确的目录下,导致Python解释器找不到。这时候需要检查文件结构,确保模块位于可以被引用的位置,比如同一目录下或者已添加到系统路径中。 如果用户确认安装正确,但仍然报错,可能需要检查模块的依赖是否满足,或者是否有权限问题。不过这些情况相对较少,常见问题还是安装缺失或路径问题。 总结下来,给用户的建议应该分步骤:首先尝试安装,如果失败,检查拼写和环境,最后考虑是否为自定义模块。此外,提供一个替代的解决方案,比如注释掉出错的代码,直接展示结果,这样用户即使没有ace_tools也能继续运行程序。</think>你遇到的错误`ModuleNotFoundError: No module named 'ace_tools'`表示Python找不到名为`ace_tools`的模块。这个问题通常有以下几种原因和解决方案: --- ### **原因分析** 1. **未安装该模块**:`ace_tools`可能是项目专用的自定义模块,或未被正确安装 2. **模块名称拼写错误**:检查是否应为其他名称(如`acetools`) 3. **环境问题**:模块安装在其他Python环境中 --- ### **分步解决方案** #### 第一步:确认模块性质 - 如果是课程/项目提供的**自定义模块**: - 检查项目文件夹中是否有`ace_tools.py`文件 - 确保代码文件和`ace_tools.py`在同一目录下 - 如果是**第三方库**: ```bash # 尝试安装(但PyPI上无此名称的包) pip install ace_tools ``` #### 第二步:临时解决方案(绕过报错) 直接注释掉报错行(第30行),改用标准方式显示DataFrame: ```python # 替换原30行代码 print("1-9月充电频次与充电时长统计:") print(result) ``` #### 第三步:长期解决方案 联系代码提供方确认: 1. `ace_tools`是否为需要单独安装的内部工具包 2. 是否需要特定的安装路径或环境配置 3. 是否有替代的依赖项(如`pandas`自带的显示方法) --- ### **补充建议** - 检查Python环境是否一致: ```python import sys print(sys.executable) # 确认当前使用的Python解释器路径 ``` - 如果是Jupyter Notebook,尝试重启内核 - 通过虚拟环境管理工具(如conda/venv)确保环境纯净 --- 如果提供更多上下文信息(如代码来源、运行环境),可以给出更精确的解决方案。
阅读全文

相关推荐

import re # 解析 MET 值的函数 def extract_met(annotation): match = re.search(r"MET (\d+\.\d+)", annotation) return float(match.group(1)) if match else None # 提取 MET 值 p001_df["MET"] = p001_df["annotation"].apply(extract_met) # 定义运动强度分类 def categorize_activity(met_value): if met_value >= 6.0: return "high" elif 3.0 <= met_value < 6.0: return "moderate" elif 1.6 <= met_value < 3.0: return "low" elif 1.0 <= met_value < 1.6: return "static" else: return "sleep" # 分类 MET 值 p001_df["activity_type"] = p001_df["MET"].apply(categorize_activity) # 计算总时长(假设每条记录代表1秒) total_duration_hours = len(p001_df) / 3600 # 计算各活动类型的时长(小时) activity_durations = p001_df["activity_type"].value_counts() / 3600 # 组织结果 result_1 = { "志愿者ID": ["P001"], "记录总时长(小时)": [total_duration_hours], "睡眠总时长(小时)": [activity_durations.get("sleep", 0)], "高等强度运动总时长(小时)": [activity_durations.get("high", 0)], "中等强度运动总时长(小时)": [activity_durations.get("moderate", 0)], "低等强度运动总时长(小时)": [activity_durations.get("low", 0)], "静态活动总时长(小时)": [activity_durations.get("static", 0)] } # 转换为 DataFrame result_df = pd.DataFrame(result_1) # 保留小数点后四位 result_df = result_df.round(4) # 保存到 Excel output_path = "/mnt/data/result_1.xlsx" result_df.to_excel(output_path, index=False) # 展示结果 import ace_tools as tools tools.display_dataframe_to_user(name="志愿者活动统计结果", dataframe=result_df) 分析代码,改用机器学习的方法解决问题

以下代码如何修改之后可以实现将结果呈现在pyside6的QtableView内?import sqlite3 import pandas as pd # 1. 创建数据库和表 def create_database(): # 连接到数据库(如果不存在则创建) conn = sqlite3.connect('example.db') cursor = conn.cursor() # 创建表 cursor.execute(''' CREATE TABLE IF NOT EXISTS employees ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, department TEXT, salary REAL ) ''') # 提交并关闭连接 conn.commit() conn.close() print("数据库和表创建成功!") # 2. 添加记录 def add_records(): conn = sqlite3.connect('example.db') cursor = conn.cursor() cursor.execute("DELETE FROM employees") # 插入数据前需要清空表,不然会引发冲突数据记录重叠问题。1-4行在第一次运行时已经存在。 # 插入多条记录 employees = [ (1, '张三', '技术部', 8500.0), (2, '李四', '市场部', 7500.0), (3, '王五', '技术部', 9200.0), (4, '赵六', '财务部', 8800.0) ] cursor.executemany(''' INSERT INTO employees (id, name, department, salary) VALUES (?, ?, ?, ?) ''', employees) conn.commit() conn.close() print("成功添加4条员工记录!") # 3. 查询并显示为DataFrame def display_as_dataframe(): conn = sqlite3.connect('example.db') # 直接读取SQL查询到DataFrame df = pd.read_sql_query("SELECT * FROM employees", conn) # 关闭连接 conn.close() # 显示DataFrame print("\n员工数据表:") print(df) # 添加数据分析 print("\n部门薪资统计:") print(df.groupby('department')['salary'].agg(['mean', 'max', 'min'])) return df # 执行所有步骤 if __name__ == "__main__": create_database() add_records() employee_df = display_as_dataframe() # 可选:导出到Excel employee_df.to_excel('employee_data.xlsx', index=False)

import pandas as pd import math as mt import numpy as np from sklearn.model_selection import train_test_split from Recommenders import SVDRecommender triplet_dataset_sub_song_merged = triplet_dataset_sub_song_mergedpd triplet_dataset_sub_song_merged_sum_df = triplet_dataset_sub_song_merged[['user','listen_count']].groupby('user').sum().reset_index() triplet_dataset_sub_song_merged_sum_df.rename(columns={'listen_count':'total_listen_count'},inplace=True) triplet_dataset_sub_song_merged = pd.merge(triplet_dataset_sub_song_merged,triplet_dataset_sub_song_merged_sum_df) triplet_dataset_sub_song_merged['fractional_play_count'] = triplet_dataset_sub_song_merged['listen_count']/triplet_dataset_sub_song_merged small_set = triplet_dataset_sub_song_merged user_codes = small_set.user.drop_duplicates().reset_index() song_codes = small_set.song.drop_duplicates().reset_index() user_codes.rename(columns={'index':'user_index'}, inplace=True) song_codes.rename(columns={'index':'song_index'}, inplace=True) song_codes['so_index_value'] = list(song_codes.index) user_codes['us_index_value'] = list(user_codes.index) small_set = pd.merge(small_set,song_codes,how='left') small_set = pd.merge(small_set,user_codes,how='left') mat_candidate = small_set[['us_index_value','so_index_value','fractional_play_count']] data_array = mat_candidate.fractional_play_count.values row_array = mat_candidate.us_index_value.values col_array = mat_candidate.so_index_value.values data_sparse = coo_matrix((data_array, (row_array, col_array)),dtype=float) K=50 urm = data_sparse MAX_PID = urm.shape[1] MAX_UID = urm.shape[0] recommender = SVDRecommender(K) U, S, Vt = recommender.fit(urm) Compute recommendations for test users uTest = [1,6,7,8,23] uTest_recommended_items = recommender.recommend(uTest, urm, 10) Output recommended songs in a dataframe recommendations = pd.DataFrame(columns=['user','song', 'score','rank']) for user in uTest: rank = 1 for song_index in uTest_recommended_items[user, 0:10]: song = small_set.loc[small_set['so_index_value'] == song_index].iloc[0] # Get song details recommendations = recommendations.append({'user': user, 'song': song['title'], 'score': song['fractional_play_count'], 'rank': rank}, ignore_index=True) rank += 1 display(recommendations)这段代码报错了,为什么?给出修改后的 代码

将上述代码放入了Recommenders.py文件中,作为一个自定义工具包。将下列代码中调用scipy包中svd的部分。转为使用Recommenders.py工具包中封装的svd方法。给出修改后的完整代码。import pandas as pd import math as mt import numpy as np from sklearn.model_selection import train_test_split from Recommenders import * from scipy.sparse.linalg import svds from scipy.sparse import coo_matrix from scipy.sparse import csc_matrix # Load and preprocess data triplet_dataset_sub_song_merged = triplet_dataset_sub_song_mergedpd # load dataset triplet_dataset_sub_song_merged_sum_df = triplet_dataset_sub_song_merged[['user','listen_count']].groupby('user').sum().reset_index() triplet_dataset_sub_song_merged_sum_df.rename(columns={'listen_count':'total_listen_count'},inplace=True) triplet_dataset_sub_song_merged = pd.merge(triplet_dataset_sub_song_merged,triplet_dataset_sub_song_merged_sum_df) triplet_dataset_sub_song_merged['fractional_play_count'] = triplet_dataset_sub_song_merged['listen_count']/triplet_dataset_sub_song_merged['total_listen_count'] # Convert data to sparse matrix format small_set = triplet_dataset_sub_song_merged user_codes = small_set.user.drop_duplicates().reset_index() song_codes = small_set.song.drop_duplicates().reset_index() user_codes.rename(columns={'index':'user_index'}, inplace=True) song_codes.rename(columns={'index':'song_index'}, inplace=True) song_codes['so_index_value'] = list(song_codes.index) user_codes['us_index_value'] = list(user_codes.index) small_set = pd.merge(small_set,song_codes,how='left') small_set = pd.merge(small_set,user_codes,how='left') mat_candidate = small_set[['us_index_value','so_index_value','fractional_play_count']] data_array = mat_candidate.fractional_play_count.values row_array = mat_candidate.us_index_value.values col_array = mat_candidate.so_index_value.values data_sparse = coo_matrix((data_array, (row_array, col_array)),dtype=float) # Compute SVD def compute_svd(urm, K): U, s, Vt = svds(urm, K) dim = (len(s), len(s)) S = np.zeros(dim, dtype=np.float32) for i in range(0, len(s)): S[i,i] = mt.sqrt(s[i]) U = csc_matrix(U, dtype=np.float32) S = csc_matrix(S, dtype=np.float32) Vt = csc_matrix(Vt, dtype=np.float32) return U, S, Vt def compute_estimated_matrix(urm, U, S, Vt, uTest, K, test): rightTerm = S*Vt max_recommendation = 10 estimatedRatings = np.zeros(shape=(MAX_UID, MAX_PID), dtype=np.float16) recomendRatings = np.zeros(shape=(MAX_UID,max_recommendation ), dtype=np.float16) for userTest in uTest: prod = U[userTest, :]*rightTerm estimatedRatings[userTest, :] = prod.todense() recomendRatings[userTest, :] = (-estimatedRatings[userTest, :]).argsort()[:max_recommendation] return recomendRatings K=50 # number of factors urm = data_sparse MAX_PID = urm.shape[1] MAX_UID = urm.shape[0] U, S, Vt = compute_svd(urm, K) # Compute recommendations for test users # Compute recommendations for test users uTest = [1,6,7,8,23] uTest_recommended_items = compute_estimated_matrix(urm, U, S, Vt, uTest, K, True) # Output recommended songs in a dataframe recommendations = pd.DataFrame(columns=['user','song', 'score','rank']) for user in uTest: rank = 1 for song_index in uTest_recommended_items[user, 0:10]: song = small_set.loc[small_set['so_index_value'] == song_index].iloc[0] # Get song details recommendations = recommendations.append({'user': user, 'song': song['title'], 'score': song['fractional_play_count'], 'rank': rank}, ignore_index=True) rank += 1 display(recommendations)

import os import gradio as gr import pandas as pd import time from pathlib import Path from datetime import datetime, date ,timedelta import tempfile import shutil from concurrent.futures import ThreadPoolExecutor, as_completed import threading import json from urllib.parse import quote from config import available_models, default_model, degrees, default_degree, GENDER, DEFAULT_GENDER, api_keys, IMAP_HOST, PORT from extract_utils import extract_resume_info, read_html_content from extract_foxmail import EmailResumeDownloader JOB_JSON_PATH = "job_descriptions.json" def update_job_description(selected_job_name): try: with open(JOB_JSON_PATH, "r", encoding="utf-8") as f: job_descriptions_latest = json.load(f) return job_descriptions_latest.get(selected_job_name, "") except Exception as e: print(f"读取岗位描述失败: {e}") return "" def download_resumes_from_mail(start_date_str=None, end_date_str=None): downloader = EmailResumeDownloader( host=IMAP_HOST, port=PORT, user=api_keys["email_user"], password=api_keys["email_pass"] ) downloader.process_emails(since_date=start_date_str, before_date=end_date_str) def process_single_resume(model_name, selected_job, job_description_input, city, file): suffix = Path(file.name).suffix.lower() content = "" temp_path = f"tmp_{threading.get_ident()}{suffix}" shutil.copy(file.name, temp_path) today_date = datetime.today().strftime("%Y-%m-%d") output_folder = os.path.join(os.path.expanduser("~"), 'Desktop', 'processed_resumes', today_date, selected_job) file_path = os.path.join(output_folder, file.name) try: if suffix == ".html": content = read_html_content(temp_path) else: return None if not content.strip(): return None if city: city = f"是否有意愿来{city}发展" job_description_input += city info = extract_resume_info(content, model_name, selected_job, job_description_input) # info["文件名"] = Path(file.name).name info["文件路径"] = file.name if not len(job_description_input): info["辅助匹配"] = 1 print(info) print("="*100) return info finally: if os.path.exists(temp_path): try: os.remove(temp_path) except Exception as e: print(f"删除临时文件 {temp_path} 失败: {e}") def dataframe_to_html_with_links(df: pd.DataFrame) -> str: df_copy = df.copy() if "文件地址" in df_copy.columns: df_copy["文件名"] = df_copy["文件地址"] df_copy.drop(columns=["文件地址"], inplace=True, errors="ignore") return df_copy.to_html(escape=False, index=False) def save_csv_to_folder(df, folder_name, save_dir): if df.empty: return None os.makedirs(save_dir, exist_ok=True) save_path = os.path.join(save_dir, f"{folder_name}.csv") with open(save_path, mode='w', encoding='utf-8-sig', newline='') as f: df.to_csv(f, index=False) temp_download_path = os.path.join(tempfile.gettempdir(), f"{folder_name}.csv") shutil.copy(save_path, temp_download_path) return temp_download_path def process_resumes_mult(model_name, selected_job, degree, job_description_input, work_experience, files, resume_limit, gender, age_min, age_max, city): start_time = time.time() degree_levels = {"大专": 1, "本科": 2, "硕士": 3, "博士": 4, "不限": 0} results, pdf_docx_files, doc_files = [], [], [] today_date = datetime.today().strftime("%Y-%m-%d") output_folder = os.path.join(os.path.expanduser("~"), 'Desktop', 'processed_resumes', today_date, selected_job) os.makedirs(output_folder, exist_ok=True) with ThreadPoolExecutor(max_workers=4) as executor: futures = [ executor.submit(process_single_resume, model_name, selected_job, job_description_input, city, file) for file in files ] for future in as_completed(futures): try: res = future.result() if res: results.append(res) except Exception as e: print(f"简历处理异常: {e}") df_filtered = pd.DataFrame(results) if not df_filtered.empty: if gender != "不限": df_filtered = df_filtered[df_filtered["性别"] == gender] # 年龄筛选:必须先确保有年龄字段 if "年龄" in df_filtered.columns: df_filtered = df_filtered[ (df_filtered["年龄"] >= age_min) & (df_filtered["年龄"] <= age_max) ] df_filtered = df_filtered[ (df_filtered["工作经验"] >= work_experience) & (df_filtered["岗位匹配度"] > 0.5) & (df_filtered["辅助匹配"] > 0.5) ] if degree != "其他": df_filtered = df_filtered[ df_filtered["学历"].map(lambda x: degree_levels.get(x, 0)) >= degree_levels[degree] ] # 合并岗位匹配度和辅助匹配,生成综合匹配得分(范围0~1) df_filtered["综合匹配得分"] = ( df_filtered["岗位匹配度"] / 2 + df_filtered["辅助匹配"] / 2 ).round(2) df_filtered = df_filtered.drop(columns=["岗位匹配度", "辅助匹配"]) df_filtered = df_filtered.sort_values(by="综合匹配得分", ascending=False) if resume_limit > 0: df_filtered = df_filtered.head(resume_limit) file_paths = df_filtered.get("文件路径") file_links = [] for file_path in file_paths: file_path = Path(file_path) file_name = file_path.name target_path = os.path.join(output_folder, file_name) file_path_str = str(file_path).replace("\\", "/") # 复制文件到输出文件夹 if file_path and os.path.exists(file_path): shutil.copy(file_path, target_path) file_links.append(file_path_str) df_filtered["文件地址"] = file_links if "文件路径" in df_filtered.columns: df_filtered = df_filtered.drop(columns=["文件路径"]) elapsed_time = f"{time.time() - start_time:.2f} 秒" return df_filtered, elapsed_time, output_folder def on_import_and_process(model_name, selected_job, degree, job_description_input, work_experience, resume_limit, gender, age_min, age_max, city): desktop = os.path.join(os.path.expanduser("~"), 'Desktop') base_dir = os.path.join(desktop, 'resumes') start_date_val = datetime.today().strftime("%Y-%m-%d") resume_folder = os.path.join(base_dir, start_date_val) file_paths = [] for suffix in [".pdf", ".doc", ".docx", ".html"]: file_paths.extend(Path(resume_folder).rglob(f"*{suffix}")) class UploadedFile: def __init__(self, path): self.name = str(path) files = [UploadedFile(path) for path in file_paths] df_filtered, elapsed_time, output_folder = process_resumes_mult( model_name, selected_job, degree, job_description_input, work_experience, files, resume_limit, gender, age_min, age_max, city ) export_button.interactive = not df_filtered.empty df_html = dataframe_to_html_with_links(df_filtered) return df_html, elapsed_time, df_filtered, output_folder def add_new_job(job_name, job_description): job_name = job_name.strip() job_description = job_description.strip() if not job_name: return "⚠️ 岗位名称不能为空" if not job_description: return "⚠️ 岗位描述不能为空" # 读取原始文件 try: with open("job_descriptions.json", "r", encoding="utf-8") as f: jobs = json.load(f) except Exception as e: return f"❌ 加载 job_descriptions.json 失败: {e}" # 如果岗位已存在 if job_name in jobs: return f"⚠️ 岗位【{job_name}】已存在,请勿重复添加" # 添加岗位 jobs[job_name] = job_description try: with open("job_descriptions.json", "w", encoding="utf-8") as f: json.dump(jobs, f, ensure_ascii=False, indent=2) except Exception as e: return f"❌ 保存失败: {e}" return gr.update(choices=list(jobs.keys())), f"✅ 成功添加岗位【{job_name}】..." def load_job_descriptions(): try: with open(JOB_JSON_PATH, "r", encoding="utf-8") as f: return json.load(f) except: return {} with gr.Blocks(title="📄 智能简历抽取 Test 版") as demo: gr.Markdown("# 📄 智能简历信息抽取") with gr.Row(): model_name = gr.Dropdown(choices=available_models, value=default_model, label="选择语言模型") degree = gr.Dropdown(choices=degrees, value=default_degree, label='学历') job_descriptions = load_job_descriptions() selected_job = gr.Dropdown(choices=list(job_descriptions.keys()), label="岗位") work_experience = gr.Slider(0, 10, value=0, step=1, label="工作经验(年数)") resume_limit = gr.Dropdown(choices=[0, 5, 10, 15, 20], value=0, label="筛选简历(0 不限制)") # 在原 Gradio UI 中添加年龄筛选区间组件: with gr.Row(): gender = gr.Dropdown(choices=GENDER, value=DEFAULT_GENDER, label='性别') city = gr.Textbox(label="城市", placeholder="请输入招聘城市名称,如 徐州") age_min = gr.Slider(18, 65, value=0, step=1, label="年龄下限") age_max = gr.Slider(18, 65, value=100, step=1, label="年龄上限") # city = gr.Textbox(label="城市", placeholder="请输入招聘城市名称,如 徐州") with gr.Accordion("➕ 添加新岗位", open=False): new_job_name = gr.Textbox(label="新岗位名称", placeholder="请输入岗位名称,如 销售经理") new_job_description = gr.Textbox( label="新岗位描述", lines=6, placeholder="请输入该岗位的要求、职责描述等,可用于简历辅助匹配" ) add_job_button = gr.Button("✅ 确认添加") add_job_output = gr.Markdown("") job_description_populate = gr.Textbox(label="岗位描述(可加入更多筛选需求)", placeholder="请输入岗位职责或要求,可用于辅助匹配", lines=3) add_job_button.click( fn=add_new_job, inputs=[new_job_name, new_job_description], outputs=[selected_job, add_job_output] ) today_str = str(date.today()) with gr.Row(): date_range = gr.Radio( choices=["今天", "最近三天", "最近一周", "最近一个月", "自定义时间段"], value="今天", label="筛选邮件时间范围" ) read_status = gr.Radio( choices=["全部", "未读", "已读"], value="全部", label="邮件读取状态" ) with gr.Row(visible=False) as custom_date_row: start_date = gr.Textbox(value=today_str, label="起始日期(格式:2025-07-16)") end_date = gr.Textbox(value=today_str, label="结束日期(格式:2025-07-16)") def toggle_date_inputs(date_range_value): return gr.update(visible=(date_range_value == "自定义时间段")) date_range.change(toggle_date_inputs, inputs=date_range, outputs=custom_date_row) with gr.Row(): import_button = gr.Button("📂 下载简历") process_button = gr.Button("🔍 开始处理") export_button = gr.Button("📥 导出筛选结果", interactive=True) download_notice = gr.Markdown(value="") # result_table = gr.Dataframe(label="筛选结果", interactive=False) result_table = gr.HTML(label="筛选结果") elapsed_time_display = gr.Textbox(label="耗时", interactive=False) output_folder_state = gr.State() result_state = gr.State() # 选岗位时更新岗位描述 def update_job_description(selected_job_name): job_descriptions = load_job_descriptions() if not selected_job_name or selected_job_name not in job_descriptions: return "" job_descriptions = load_job_descriptions() return job_descriptions[selected_job_name] selected_job.change( fn=update_job_description, inputs=[selected_job], outputs=[job_description_populate] ) def on_download_and_import(model_name, selected_job, degree, job_description_input, work_experience, resume_limit, gender, age_min, age_max, city): return on_import_and_process(model_name, selected_job, degree, job_description_input, work_experience, resume_limit, gender, age_min, age_max, city) def show_downloading_text(): return "⏳ 开始下载中..." def on_download_email(date_range_value, start_date_val, end_date_val, read_status_val): today = datetime.today().date() if date_range_value == "今天": start = today end = today elif date_range_value == "最近三天": start = today - timedelta(days=2) end = today elif date_range_value == "最近一周": start = today - timedelta(days=6) end = today elif date_range_value == "最近一个月": start = today - timedelta(days=29) end = today elif date_range_value == "自定义时间段": try: start = datetime.strptime(start_date_val, "%Y-%m-%d").date() end = datetime.strptime(end_date_val, "%Y-%m-%d").date() except Exception: return "⚠️ 自定义时间格式错误,请使用 YYYY-MM-DD" else: return "⚠️ 未知时间范围选项" # 邮件读取状态控制 unseen_only = None if read_status_val == "未读": unseen_only = True elif read_status_val == "已读": unseen_only = False downloader = EmailResumeDownloader( host=IMAP_HOST, port=PORT, user=api_keys["email_user"], password=api_keys["email_pass"] ) downloader.process_emails( since_date=start.strftime("%Y-%m-%d"), before_date=(end + timedelta(days=1)).strftime("%Y-%m-%d"), # 邮件before_date是“非包含” unseen_only=unseen_only ) return f"📥 已下载 {start} 至 {end} 区间、状态为 [{read_status_val}] 的简历 ✅" import_button.click( fn=show_downloading_text, outputs=[download_notice] ).then( fn=on_download_email, inputs=[date_range, start_date, end_date, read_status], outputs=[download_notice] ) process_button.click( fn=on_download_and_import, inputs=[model_name, selected_job, degree, job_description_populate, work_experience, resume_limit, gender, age_min, age_max, city], outputs=[result_table, elapsed_time_display, result_state, output_folder_state] ) def export_csv(df, selected_job, output_folder): return save_csv_to_folder(df, selected_job, output_folder) export_button.click( fn=export_csv, inputs=[result_state, selected_job, output_folder_state], outputs=gr.File(label="下载 CSV") ) if __name__ == "__main__": demo.launch(server_name="0.0.0.0", share=True, debug=True, allowed_paths=[os.path.join(os.path.expanduser("~"), 'Desktop')])如何在result_table = gr.HTML(label="筛选结果")每行后面添加一个按钮,按钮使用gradio库进行添加,然后绑定这一行文件位置这一数值进行触发

import pandas as pd # 读取CSV文件,假设文件路径和文件内容格式正确 data = pd.read_csv("/Metro_train/record_2019-01-07.csv", header=None) # 设置列名 data.columns = ['time', 'line', 'station', 'device', 'status', 'user', 'type'] # 创建一个新的DataFrame,用于后续的排序和处理 df = pd.DataFrame(data) # 根据'user'和'time'列对df进行排序 df_sorted = df.sort_values(by=['user', 'time'], ascending=[True, False]) # 创建新的列,用于存储下一行的用户ID、时间、线路、设备、状态和类型 df_sorted['user1'] = df_sorted["user"].shift(+1) df_sorted['time_out'] = df_sorted["time"].shift(+1) df_sorted['line_out'] = df_sorted['line'].shift(+1) df_sorted['device_out'] = df_sorted['device'].shift(+1) df_sorted['status_out'] = df_sorted['status'].shift(+1) df_sorted['type_out'] = df_sorted['type'].shift(+1) df_sorted['station_out'] = df_sorted['station'].shift(+1) # 查看df_sorted的前几行和后几行 print(df_sorted.head()) print(df_sorted.tail()) # 筛选出同一用户的连续记录,并且当前行为进站(status=1),下一行为出站(status_out=0) df_sorted = df_sorted[(df_sorted['user'] == df_sorted['user1']) & (df_sorted['status'] == 1) & (df_sorted['status_out'] == 0)] # 将'time'和'time_out'列转换为datetime类型,以便进行时间的减法运算 df_sorted['time'] = pd.to_datetime(df_sorted['time']) df_sorted['time_out'] = pd.to_datetime(df_sorted['time_out']) # 计算乘坐时间,单位为秒 df_sorted['duration'] = (df_sorted['time_out'] - df_sorted['time']).dt.total_seconds() # 查看结果 print(df_sorted[['user', 'time', 'time_out', 'duration',]]) time_seconds = df_sorted['time_out'].dt.time.apply( lambda x: x.hour*3600 + x.minute*60 + x.second ) df_filtered = df_sorted[ (df_sorted['station'].between(1, 80) ) & (df_sorted['station_out'].between(16,16)) & (time_seconds.between(28800, 32400)) ] df_filtered_sorted = df_filtered.sort_values(by='time', ascending=True) # 查看结果 pd.set_option('display.max_rows', 100) # 设置显示最大行数为None,即不限制显示行数 print(df_filtered_sorted[['time_out', 'line', 'station', 'line_out','station_out','duration_minutes']]) 我想把筛选得到的结果中的time_out和line两列单独存成csv,怎么做

import oml import xml.etree.ElementTree as ET from datetime import datetime, timedelta import re import numpy as np import pandas as pd import warnings import os import matplotlib.pyplot as plt from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler import seaborn as sns import matplotlib.dates as mdates warnings.filterwarnings('ignore') PIC_DIR = "pic" if not os.path.exists(PIC_DIR): os.makedirs(PIC_DIR) def format_timestamp(ts): """格式化时间戳为Oracle可接受的字符串""" return ts.strftime("%Y-%m-%d %H:%M:%S.%f")[:23] def is_holiday(date): """判断日期是否为休息日(简化实现)""" # 在实际应用中,这里应该连接到日历表或API # 周末判断(周六=5,周日=6) if date.weekday() in [5, 6]: return True # 这里可以添加特定假期的判断 # 例如:if date.month == 1 and date.day == 1: return True return False def find_previous_workday(target_date): """找到前一个工作日""" current = target_date - timedelta(days=1) while is_holiday(current): current -= timedelta(days=1) return current def calculate_std_dev(df, exclude_columns): """计算数据框的标准差(排除指定列)""" # 排除不需要的列 numeric_cols = [col for col in df.columns if col not in exclude_columns] if not numeric_cols: return 0, {} # 计算每列的标准差 col_std = {} for col in numeric_cols: if df[col].dtype in [np.int64, np.float64]: col_std[col] = df[col].std() # 计算所有数值列合并后的标准差 all_values = [] for col in numeric_cols: if df[col].dtype in [np.int64, np.float64]: all_values.extend(df[col].values) total_std = np.std(all_values) if all_values else 0 return total_std, col_std def get_relative_change(current, reference): """计算相对变化率,处理除零问题""" if reference == 0 and current == 0: return 0 # 两者都为零,无变化 elif reference == 0: return float('inf') # 参考值为零,当前值非零 else: return abs(current - reference) / reference * 100 def classify_change(change_rate): """分类变化率,处理无穷大情况""" if change_rate == float('inf'): return "极端变化(参考值为零)", "m4" elif change_rate < 5: return "正常波动(可忽略)", "m1" elif 5 <= change_rate < 10: return "值得关注的变化", "m2" elif 10 <= change_rate < 20: return "显著变化", "m3" else: return "重大变化", "m4" def format_change_rate(change_rate): """格式化变化率显示""" if change_rate == float('inf'): return "无穷大" else: return f"{change_rate:.2f}%" def get_data_samples(cursor, obj_name, inst_number, target_time, num_samples=10): """获取目标时间点之前的样本数据,处理标准差为零的情况""" # 获取目标时间点之前的10个采样点 query = f""" SELECT * FROM "{obj_name}" WHERE instance_number = {inst_number} AND snap_time < TO_TIMESTAMP('{format_timestamp(target_time)}', 'YYYY-MM-DD HH24:MI:SS.FF3') ORDER BY snap_time DESC FETCH FIRST {num_samples} ROWS ONLY """ cursor.execute(query) result = cursor.fetchall() if not result: return None, None, None # 获取列名 col_names = [col[0] for col in cursor.description] # 创建DataFrame df = pd.DataFrame(result, columns=col_names) # 排除不需要的列 exclude_cols = ['SNAP_TIME', 'INSTANCE_NUMBER', 'PRED', 'PROB', 'ANOMALYDETAILS'] # 计算标准差 total_std, col_std = calculate_std_dev(df, exclude_cols) # 如果标准差为零,添加微小噪声避免除零错误 if total_std == 0: total_std = 1e-6 for col in col_std: if col_std[col] == 0: col_std[col] = 1e-6 return df, total_std, col_std def generate_description(attr): """生成指标问题描述""" rank = attr['rank'] weight = attr['weight'] name = attr['name'] if rank == 1: return f"主要异常指标({weight*100:.0f}%权重),直接影响系统稳定性" elif weight > 0.7: return f"关键异常指标({weight*100:.0f}%权重),需要立即处理" elif weight > 0.3: return f"重要异常指标({weight*100:.0f}%权重),建议优先排查" else: return f"参考指标({weight*100:.0f}%权重),可作为辅助分析" def escape_sql(text): """转义SQL中的特殊字符""" return text.replace("'", "''").replace("\n", " ") def analyze_anomalies(): cursor = None conn = None obj_name, anom_time, anom_inst = None, None, None pic_paths = [] # 存储生成的图片路径 m4_indicators = [] # 存储m4级别的指标名称 try: # 1. 连接数据库 conn = oml.connect(user='aiopsoraoem', password='oracle', host='localhost', port=1521, service_name='aiopspdb') print("数据库连接成功") # 创建游标对象用于执行SQL cursor = oml.cursor() # 2. 获取待分析的异常记录 query = """ SELECT object_name, anomalies_time, anomalies_inst_number FROM RT_RESULT WHERE alert_analysis_status IS NULL AND anomalies_time > SYSDATE - 10/1440 ORDER BY anomalies_time DESC FETCH FIRST 1 ROWS ONLY """ # 执行查询 cursor.execute(query) result = cursor.fetchall() if not result: print("没有需要分析的异常记录") return # 提取数据 obj_name = result[0][0] anom_time = result[0][1] anom_inst = int(result[0][2]) print(f"分析记录: {obj_name} at {anom_time} (实例 {anom_inst})") # 3. 更新状态为doing update_query = f""" UPDATE RT_RESULT SET alert_analysis_status = 'doing' WHERE object_name = '{escape_sql(obj_name)}' AND anomalies_time = TO_TIMESTAMP('{format_timestamp(anom_time)}', 'YYYY-MM-DD HH24:MI:SS.FF3') AND anomalies_inst_number = {anom_inst} """ cursor.execute(update_query) cursor.connection.commit() print("状态更新为doing") # 4. 获取异常详情 detail_query = f""" SELECT ANOMALYDETAILS FROM "{obj_name}" WHERE snap_time = TO_TIMESTAMP('{format_timestamp(anom_time)}', 'YYYY-MM-DD HH24:MI:SS.FF3') AND instance_number = {anom_inst} """ # 执行查询 cursor.execute(detail_query) details_result = cursor.fetchall() if not details_result: raise Exception(f"{obj_name}表中未找到匹配记录") # 获取XML数据 xml_data = details_result[0][0] # 处理XML命名空间问题 xml_data = re.sub(r'\sxmlns="[^"]+"', '', xml_data, count=1) root = ET.fromstring(xml_data) attributes = [] for attr in root.findall('.//Attribute'): try: attr_data = { 'name': attr.get('name'), 'value': float(attr.get('actualValue')), 'weight': float(attr.get('weight')), 'rank': int(attr.get('rank')) } attributes.append(attr_data) except (TypeError, ValueError) as e: print(f"解析属性时出错: {e}") # 按rank排序 attributes.sort(key=lambda x: x['rank']) # 5. 交叉验证 - 获取异常点前10个采样点 print("获取异常点前10个采样点数据...") anomaly_df, B_total_std, C_col_std = get_data_samples(cursor, obj_name, anom_inst, anom_time) if anomaly_df is None: raise Exception(f"未找到异常点前10个采样点数据: {obj_name} at {anom_time} (实例 {anom_inst})") # 6. 获取正常模式数据 print("获取正常模式数据...") # P1: 一天前相同时间点(跳过休息日) p1_time = anom_time - timedelta(days=1) if is_holiday(p1_time): p1_time = find_previous_workday(p1_time) day_ago_df, A1_total_std, A2_col_std = get_data_samples(cursor, obj_name, anom_inst, p1_time) # P2: 一周前相同时间点 p2_time = anom_time - timedelta(weeks=1) week_ago_df, B1_total_std, B2_col_std = get_data_samples(cursor, obj_name, anom_inst, p2_time) # P3: 一个月前相同日期(跳过休息日) p3_time = anom_time - timedelta(days=30) if is_holiday(p3_time): p3_time = find_previous_workday(p3_time) month_ago_df, C1_total_std, C2_col_std = get_data_samples(cursor, obj_name, anom_inst, p3_time) # 如果正常模式数据为空,使用默认值 if day_ago_df is None: print("警告: 未找到一天前相同时间点数据") day_ago_df = pd.DataFrame() A1_total_std, A2_col_std = 0, {} if week_ago_df is None: print("警告: 未找到一周前相同时间点数据") week_ago_df = pd.DataFrame() B1_total_std, B2_col_std = 0, {} if month_ago_df is None: print("警告: 未找到一个月前相同日期数据") month_ago_df = pd.DataFrame() C1_total_std, C2_col_std = 0, {} # 7. 生成验证结果 validation_results = "\n\n===== 交叉验证结果 =====\n" m4_changes = [] m4_indicator_set = set() # 用于存储m4级别的指标名称 # 第一轮验证:总标准差比较 validation_results += "\n第一轮验证:总标准差比较\n" validation_results += f"异常模式总标准差 (B): {B_total_std:.4f}\n" # 比较1:B vs A1 if A1_total_std is not None: change_rate = get_relative_change(B_total_std, A1_total_std) change_desc, change_class = classify_change(change_rate) rate_str = format_change_rate(change_rate) comp_result = f"与一天前相同时间点 (A1: {A1_total_std:.4f}) 比较: 变化率 {rate_str} - {change_desc} ({change_class})\n" validation_results += comp_result if change_class == "m4": m4_changes.append(comp_result.strip()) #m4_indicator_set.add("总标准差") # 比较2:B vs B1 if B1_total_std is not None: change_rate = get_relative_change(B_total_std, B1_total_std) change_desc, change_class = classify_change(change_rate) rate_str = format_change_rate(change_rate) comp_result = f"与一周前相同时间点 (B1: {B1_total_std:.4f}) 比较: 变化率 {rate_str} - {change_desc} ({change_class})\n" validation_results += comp_result if change_class == "m4": m4_changes.append(comp_result.strip()) #m4_indicator_set.add("总标准差") # 比较3:B vs C1 if C1_total_std is not None: change_rate = get_relative_change(B_total_std, C1_total_std) change_desc, change_class = classify_change(change_rate) rate_str = format_change_rate(change_rate) comp_result = f"与一个月前相同日期 (C1: {C1_total_std:.4f}) 比较: 变化率 {rate_str} - {change_desc} ({change_class})\n" validation_results += comp_result if change_class == "m4": m4_changes.append(comp_result.strip()) #m4_indicator_set.add("总标准差") # 第二轮验证:各列标准差比较 validation_results += "\n第二轮验证:各列标准差比较\n" # 只分析排名前3的指标 top_attributes = attributes[:3] for attr in top_attributes: col_name = attr['name'] validation_results += f"\n指标: {col_name} (当前值: {attr['value']}, 权重: {attr['weight']:.2f}, 排名: {attr['rank']})\n" # 异常模式该列标准差 col_std_b = C_col_std.get(col_name, 0) validation_results += f"异常模式标准差 (C): {col_std_b:.4f}\n" # 比较1:C vs A2 if A2_col_std and col_name in A2_col_std: col_std_a2 = A2_col_std[col_name] change_rate = get_relative_change(col_std_b, col_std_a2) change_desc, change_class = classify_change(change_rate) rate_str = format_change_rate(change_rate) comp_result = f"与一天前相同时间点 (A2: {col_std_a2:.4f}) 比较: 变化率 {rate_str} - {change_desc} ({change_class})\n" validation_results += comp_result if change_class == "m4": m4_changes.append(f"指标 {col_name}: {comp_result.strip()}") m4_indicator_set.add(col_name) # 比较2:C vs B2 if B2_col_std and col_name in B2_col_std: col_std_b2 = B2_col_std[col_name] change_rate = get_relative_change(col_std_b, col_std_b2) change_desc, change_class = classify_change(change_rate) rate_str = format_change_rate(change_rate) comp_result = f"与一周前相同时间点 (B2: {col_std_b2:.4f}) 比较: 变化率 {rate_str} - {change_desc} ({change_class})\n" validation_results += comp_result if change_class == "m4": m4_changes.append(f"指标 {col_name}: {comp_result.strip()}") m4_indicator_set.add(col_name) # 比较3:C vs C2 if C2_col_std and col_name in C2_col_std: col_std_c2 = C2_col_std[col_name] change_rate = get_relative_change(col_std_b, col_std_c2) change_desc, change_class = classify_change(change_rate) rate_str = format_change_rate(change_rate) comp_result = f"与一个月前相同日期 (C2: {col_std_c2:.4f}) 比较: 变化率 {rate_str} - {change_desc} ({change_class})\n" validation_results += comp_result if change_class == "m4": m4_changes.append(f"指标 {col_name}: {comp_result.strip()}") m4_indicator_set.add(col_name) # 7. 数据可视化 # 创建特定目录保存图片 timestamp_str = anom_time.strftime("%Y%m%d_%H%M%S") save_dir = os.path.join(PIC_DIR, f"{obj_name}_{anom_inst}_{timestamp_str}") if not os.path.exists(save_dir): os.makedirs(save_dir) # 准备所有样本数据用于聚类 all_data = [] labels = [] # 添加正常模式样本(只添加非空数据) if day_ago_df is not None and not day_ago_df.empty: all_data.append(day_ago_df) labels.extend(['Day Ago'] * len(day_ago_df)) if week_ago_df is not None and not week_ago_df.empty: all_data.append(week_ago_df) labels.extend(['Week Ago'] * len(week_ago_df)) if month_ago_df is not None and not month_ago_df.empty: all_data.append(month_ago_df) labels.extend(['Month Ago'] * len(month_ago_df)) # 添加异常样本 if anomaly_df is not None and not anomaly_df.empty: all_data.append(anomaly_df) labels.extend(['Anomaly'] * len(anomaly_df)) # 检查是否有足够数据 if len(all_data) == 0: print("警告: 没有足够的数据进行可视化") else: # 合并所有数据 combined_df = pd.concat(all_data, ignore_index=True) # 排除非数值列 exclude_cols = ['SNAP_TIME', 'INSTANCE_NUMBER', 'PRED', 'PROB', 'ANOMALYDETAILS'] numeric_cols = [col for col in combined_df.columns if col not in exclude_cols and combined_df[col].dtype in [np.int64, np.float64]] if not numeric_cols: print("警告: 没有数值列可用于可视化") else: # 提取数值数据 data = combined_df[numeric_cols].values # 标准化数据 scaler = StandardScaler() scaled_data = scaler.fit_transform(data) # 使用K-Means聚类 kmeans = KMeans(n_clusters=2, random_state=42) clusters = kmeans.fit_predict(scaled_data) # 使用PCA降维可视化 from sklearn.decomposition import PCA pca = PCA(n_components=2) reduced_data = pca.fit_transform(scaled_data) # 创建聚类图 plt.figure(figsize=(12, 8)) # 为不同标签使用不同颜色 colors = {'Day Ago': 'blue', 'Week Ago': 'green', 'Month Ago': 'purple', 'Anomaly': 'red'} # 绘制所有点 for label in set(labels): idx = [i for i, l in enumerate(labels) if l == label] plt.scatter(reduced_data[idx, 0], reduced_data[idx, 1], c=colors[label], label=label, alpha=0.6) # 标记异常点(红色) anomaly_idx = [i for i, l in enumerate(labels) if l == 'Anomaly'] plt.scatter(reduced_data[anomaly_idx, 0], reduced_data[anomaly_idx, 1], c='red', marker='x', s=100, label='Anomaly Points') plt.title('K-Means Clustering of System Metrics') plt.xlabel('Principal Component 1') plt.ylabel('Principal Component 2') plt.legend() plt.grid(True) # 保存聚类图 kmeans_path = os.path.join(save_dir, 'kmeans_clustering.png') plt.savefig(kmeans_path) plt.close() pic_paths.append(kmeans_path) print(f"保存K-Means聚类图到: {kmeans_path}") # 创建时间序列图 plt.figure(figsize=(15, 10)) # 为每个指标创建子图 num_plots = min(5, len(numeric_cols)) # 最多显示5个指标 fig, axes = plt.subplots(num_plots, 1, figsize=(15, 5*num_plots)) if num_plots == 1: axes = [axes] # 确保单图时axes是列表 for i, col in enumerate(numeric_cols[:num_plots]): ax = axes[i] # 绘制正常模式(只绘制非空数据) if day_ago_df is not None and not day_ago_df.empty: day_ago_df['SNAP_TIME'] = pd.to_datetime(day_ago_df['SNAP_TIME']) ax.plot(day_ago_df['SNAP_TIME'], day_ago_df[col], 'b-', label='Day Ago', alpha=0.7) if week_ago_df is not None and not week_ago_df.empty: week_ago_df['SNAP_TIME'] = pd.to_datetime(week_ago_df['SNAP_TIME']) ax.plot(week_ago_df['SNAP_TIME'], week_ago_df[col], 'g-', label='Week Ago', alpha=0.7) if month_ago_df is not None and not month_ago_df.empty: month_ago_df['SNAP_TIME'] = pd.to_datetime(month_ago_df['SNAP_TIME']) ax.plot(month_ago_df['SNAP_TIME'], month_ago_df[col], 'm-', label='Month Ago', alpha=0.7) # 绘制异常点 if anomaly_df is not None and not anomaly_df.empty: anomaly_df['SNAP_TIME'] = pd.to_datetime(anomaly_df['SNAP_TIME']) # 只绘制异常点 ax.scatter(anomaly_df['SNAP_TIME'], anomaly_df[col], c='red', s=50, label='Anomaly', zorder=5) ax.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d %H:%M')) ax.xaxis.set_major_locator(mdates.AutoDateLocator()) ax.set_title(f'Time Series: {col}') ax.set_xlabel('Time') ax.set_ylabel('Value') ax.legend(loc='upper right') ax.grid(True, linestyle='--', alpha=0.7) plt.setp(ax.get_xticklabels(), rotation=45, ha='right') plt.tight_layout() # 保存时间序列图 timeseries_path = os.path.join(save_dir, 'timeseries_comparison.png') plt.savefig(timeseries_path, bbox_inches='tight') plt.close() pic_paths.append(timeseries_path) print(f"保存时间序列图到: {timeseries_path}") # 评估哪种可视化更直观 evaluation = """ === 可视化方法评估 === 1. K-Means聚类图: - 优点: 展示数据点在高维空间的分布,可以清晰看到异常点与正常模式的分离 - 缺点: 需要降维处理,可能丢失部分信息;不直观展示时间变化 2. 时间序列图: - 优点: 直观展示指标随时间的变化趋势,容易识别异常点 - 缺点: 当指标过多时难以在同一图中展示 结论: 时间序列图更直观展示异常点信息,特别是当需要分析指标随时间的变化趋势时。 建议将时间序列图作为主要可视化工具,聚类图作为辅助分析工具。 """ print(evaluation) # 保存评估结论 with open(os.path.join(save_dir, 'visualization_evaluation.txt'), 'w') as f: f.write(evaluation) # 8. 生成分析报告 analysis_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") summary = f"检测到{len(attributes)}个异常指标" if m4_changes: summary += ",检测到指标有重大变化,请及时关注!" m4_warning = "\n\n===== 重大变化警告 =====\n" for i, change in enumerate(m4_changes, 1): m4_warning += f"{i}. {change}\n" summary += m4_warning details = f"异常分析报告 ({analysis_time})\n" details += f"目标对象: {obj_name}\n" details += f"异常时间: {anom_time}\n" details += f"实例编号: {anom_inst}\n" details += "="*50 + "\n" # 添加指标详情 for attr in attributes: details += ( f"指标 {attr['rank']}: {attr['name']}\n" f" 当前值: {attr['value']}\n" f" 影响权重: {attr['weight']:.2f}\n" f" 问题描述: {generate_description(attr)}\n\n" ) # 添加交叉验证结果 details += validation_results # 添加数据样本信息 details += "\n\n===== 数据样本详情 =====\n" # 定义要排除的列 exclude_columns = ['ANOMALYDETAILS'] # 异常点前10个采样点 if anomaly_df is not None: # 排除指定列 anomaly_df_display = anomaly_df.drop(columns=exclude_columns, errors='ignore') details += f"异常点前10个采样点:\n{anomaly_df_display.to_string()}\n\n" else: details += "异常点前10个采样点: 无数据\n\n" # 一天前相同时间点采样点 if day_ago_df is not None: # 排除指定列 day_ago_df_display = day_ago_df.drop(columns=exclude_columns, errors='ignore') details += f"一天前相同时间点采样点:\n{day_ago_df_display.to_string()}\n\n" else: details += "一天前相同时间点采样点: 无数据\n\n" # 一周前相同时间点采样点 if week_ago_df is not None: # 排除指定列 week_ago_df_display = week_ago_df.drop(columns=exclude_columns, errors='ignore') details += f"一周前相同时间点采样点:\n{week_ago_df_display.to_string()}\n\n" else: details += "一周前相同时间点采样点: 无数据\n\n" # 一个月前相同日期采样点 if month_ago_df is not None: # 排除指定列 month_ago_df_display = month_ago_df.drop(columns=exclude_columns, errors='ignore') details += f"一个月前相同日期采样点:\n{month_ago_df_display.to_string()}\n" else: details += "一个月前相同日期采样点: 无数据\n" print(details) # 9. 更新分析结果 # 根据是否有重大变化设置alert_status alert_status = "sending" if m4_changes else "normal" # 准备图片路径字符串 pic_paths_str = ",".join(pic_paths) if pic_paths else "无图片" # 准备m4指标字符串 m4_indicators_str = ",".join(m4_indicator_set) if m4_indicator_set else "无" update_query = """ UPDATE RT_RESULT SET alert_analysis_status = :status, alert_analysis_summary = :summary, alert_analysis_detail = :detail, alert_status = :alert_status, alert_analysis_pic = :pic_paths, alert_analysis_dar = :m4_indicators WHERE object_name = :obj_name AND anomalies_time = TO_TIMESTAMP(:anom_time, 'YYYY-MM-DD HH24:MI:SS.FF3') AND anomalies_inst_number = :inst_number """ params = { 'status': 'done', 'summary': summary, 'detail': details, 'alert_status': alert_status, 'pic_paths': pic_paths_str, 'm4_indicators': m4_indicators_str, 'obj_name': obj_name, 'anom_time': format_timestamp(anom_time), 'inst_number': anom_inst } cursor.execute(update_query, params) cursor.connection.commit() print("分析结果保存成功") print(f"图片路径: {pic_paths_str}") print(f"m4指标: {m4_indicators_str}") print(details) except Exception as e: print(f"处理失败: {str(e)}") # 更新状态为error if obj_name and anom_time and anom_inst is not None: error_update = f""" UPDATE RT_RESULT SET alert_analysis_status = 'error' WHERE object_name = '{escape_sql(obj_name)}' AND anomalies_time = TO_TIMESTAMP('{format_timestamp(anom_time)}', 'YYYY-MM-DD HH24:MI:SS.FF3') AND anomalies_inst_number = {anom_inst} """ try: if cursor: cursor.execute(error_update) cursor.connection.commit() except Exception as inner_e: print(f"更新错误状态时出错: {str(inner_e)}") finally: # 关闭游标 if cursor: cursor.close() # 关闭连接 if conn: conn.close() print("数据库连接关闭") if __name__ == "__main__": analyze_anomalies() ====================== 以上代码在《创建时间序列图》这段代码中画出来的图上图所示 都是按天,我的采样点比较密集,就容易混到一起,是否能按照我采集的数据来做图, 没有日期显示的就不用画出图了,也就是绘制时间序列时跳过无数据的区间。 请修改以上的相关代码段

在以下代码的基础上,增加内容:绘制PDP的置信区间。import time import pandas as pd from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error, r2_score import matplotlib.pyplot as plt from sklearn.inspection import PartialDependenceDisplay import numpy as np from scipy.interpolate import UnivariateSpline # 导入平滑拟合函数 file_path = 'data11.csv' data = pd.read_csv(file_path, encoding='gbk') #-----------------------夏季-----------------------# ########poi2!!poi2!! # 设定特征和目标变量 X = data[['Distance to transit','Network betweenness','Population density','POI', 'PM2.5_summer','Temperature_summer','GVI_summer','SVF_summer']] # 选择所有列,除了最后一列作为特征 y = data['User intensity (summer)'] # 选择最后一列作为目标变量 # 划分数据集为训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 创建随机森林回归模型 model = RandomForestRegressor(criterion='squared_error', max_depth=None, min_samples_leaf=1, min_samples_split=2, n_estimators=100, bootstrap=True, oob_score=True, max_features=None) model.fit(X_train, y_train) # 获取特征重要性 importances = model.feature_importances_ # 创建一个DataFrame以便于查看和排序 features = X.columns importance_df = pd.DataFrame({'Feature': features, 'Importance': importances}) importance_df = importance_df.sort_values(by='Importance', ascending=False) print(importance_df) # 进行预测 y_pred = model.predict(X_test) # 计算均方误差 mse = mean_squared_error(y_test, y_pred) print(f'Mean Squared Error: {mse}') # 计算 R^2 值 r2 = r2_score(y_test, y_pred) print(f'R^2 Score: {r2}') feature_list = ['Distance to transit','Network betweenness','Population density','POI', 'PM2.5_summer','Temperature_summer','GVI_summer','SVF_summer'] abc_list = ["(a)","(b)","(c)","(d)","(e)","(f)","(g)","(h)"] # 创建4行2列的子图 fig, axes = plt.subplots(4, 2, figsize=(12, 16)) # 创建4行2列的图形,调整尺寸 # 在子图中绘制部分依赖图 for ind in range(len(feature_list)): feat_name = feature_list[ind] row = ind // 2 # 确定当前子图所在行 col = ind % 2 # 确定当前子图所在列 ax = axes[row, col] # 获取当前的子图轴 # 生成部分依赖图 display = PartialDependenceDisplay.from_estimator(model, X_test, [feat_name]) # 设置原始部分依赖图线条为灰色 display.plot(ax=ax) # 获取部分依赖图的线条对象,修改颜色为红色 for line in ax.lines: line.set_color('r') # 将线条颜色设置为红色 # 确保用的是正确的坐标轴对象 if isinstance(display.axes_, np.ndarray): ax = display.axes_[0, 0] # 获取部分依赖图的数据 # x_values: 特征值,y_values: 预测值 x_values = display.pd_results[0]['grid_values'][0] # 获取 x 值 y_values = display.pd_results[0]['average'][0] # 获取 y 值 # 使用UnivariateSpline对数据进行平滑拟合 spline = UnivariateSpline(x_values, y_values, s=600) # s为平滑参数,可以根据需要调整 smooth_y_values = spline(x_values) # 计算拟合的y值 # 绘制平滑曲线,并设置为红色 ax.plot(x_values, smooth_y_values, color='grey', linestyle='--', label='Smoothed Fit') # 设置标签和坐标轴名称的字号 xlabel_name = abc_list[ind]+' ' + feat_name ax.set_xlabel(xlabel_name, fontsize=12, fontfamily='Arial') ax.set_ylabel('User intensity (summer)', fontsize=12, fontfamily='Arial') # 设定坐标轴刻度的字号 ax.tick_params(axis='both', which='major', labelsize=10) # 添加网格 ax.grid(True, which='both', linestyle='--', linewidth=0.7) # 设置网格线样式和宽度 # 调整图形布局,避免内容被裁剪 plt.tight_layout() # 保存图像 #plt.savefig('figure/summer_all_features.png', format='png', dpi=960) # 显示图像 plt.show()

# https://search.dangdang.com/?key=%B1%E0%B3%CC&act=input&page_index=1 # 导入requests import requests import time from bs4 import BeautifulSoup import mysql.connector import csv # 定义容器 用来存储所有数据 allContainer = [] for i in range(1, 36): # 判断当前是否为第一次循环 if i == 1: url = "https://search.dangdang.com/?key=%B1%E0%B3%CC&act=input&page_index=1" else: url = f"https://search.dangdang.com/?key=%B1%E0%B3%CC&act=input&page_index={i}" print(f"当前已完成第{i}次") # 循环休眠 防止检测 time.sleep(1) # 发起请求 # 请求头 header = { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", "Cache-Control": "max-age=0", "Connection": "keep-alive", "Cookie": "ddscreen=2; __permanent_id=20250609184530979156760224679480468; __visit_id=20250609184530993130404124438448889; __out_refer=1749465931%7C!%7Cwww.baidu.com%7C!%7C; dest_area=country_id%3D9000%26province_id%3D111%26city_id%3D0%26district_id%3D0%26town_id%3D0; __rpm=s_112100.155956512835%2C155956512836..1749466159510%7Cs_112100.155956512835%2C155956512836..1749466166450; search_passback=1e0bf85a587c99ab37bc4668fc0100003945670025bc4668; __trace_id=20250609184927332100480187110221325", "Host": "search.dangdang.com", "Referer": "https://search.dangdang.com/?key=%B1%E0%B3%CC&act=input&page_index=2", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-User": "?1", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0", } response = requests.get(url, headers=header) # 设置响应的编码格式 # response.encoding = 'utf-8' # 自动识别编码方式(关键!) response.encoding = response.apparent_encoding # 将响应先保存至本地,然后先测试对本地页面进行数据解析 然后再进行多次爬取 # with open('../data/当当网.html', 'w', encoding='utf-8') as f: # f.write(response.text) htmlTree = BeautifulSoup(response.text, 'html.parser') allulEle = htmlTree.find_all('ul', class_="bigimg") for ul in allulEle: # 根据每一个ul标签中的li 进行指定数据的获取 allw1 = ul.find_all('li', recursive=False) # 获取w1下的p标签 for li_tag in allw1: rowContainer = [] # 提取书名 title_tag = li_tag.find_all('p', class_='name') if title_tag: a_tag = li_tag.find_all('a') if a_tag: title = a_tag[0].get('title') href = a_tag[0].get('href') link = f"https:{href}" rowContainer.append(title) rowContainer.append(link) else: title = "" href = "" else: title = "" href = "" pre_price = li_tag.find_all('span', class_='search_pre_price') for p in pre_price: PrePrice = p.get_text(strip=True) rowContainer.append(PrePrice) # 提取评论数 comment_count = li_tag.find('a', class_='search_comment_num') if comment_count: CommentCount = comment_count.get_text(strip=True) else: CommentCount = '0条评论' rowContainer.append(CommentCount) # 提取作者、出版时间、出版社 author_info = li_tag.find('p', class_='search_book_author') for p in author_info: AuthorInfo = p.get_text(strip=True).replace('\\\\', '').replace('/', '') if not AuthorInfo: AuthorInfo = '' rowContainer.append(AuthorInfo) allContainer.append(rowContainer) for i in allContainer: print(i) # 导入数据库模块 import mysql.connector # 使用当前库中的内置对象来创建数据库连接 mydb = mysql.connector.connect( host='localhost', # 当前mysql运行服务的地址 port=3306, # mysql服务的端口号 user='root', # mysql用户名 password='root', # 密码 database='dangdang' ) # 创建游标对象 mycursor = mydb.cursor() # discount VARCHAR ( 20 ), -- 折扣 # 创建图书信息表 create_table_sql = """ CREATE TABLE IF NOT EXISTS books ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR ( 255 ) NOT NULL, -- 书名 link VARCHAR ( 512 ), -- 链接 now_price VARCHAR ( 20 ), -- 现价 comment_count VARCHAR ( 50 ), -- 评论数 author VARCHAR ( 100 ), -- 作者 publish_date VARCHAR ( 20 ), -- 出版时间 publisher VARCHAR ( 100 ), -- 出版社 action VARCHAR ( 100 ), unidentified VARCHAR ( 20 ) ) """ # 执行建表语句 mycursor.execute(create_table_sql) # 插入语句 insert_sql = """ INSERT INTO books (title, link, now_price, comment_count, author, publish_date, publisher, action, unidentified) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) """ for book in allContainer: if len(book) == 8: book.insert(4, '') mycursor.execute(insert_sql, list(book)) # 提交事务 mydb.commit() print("✅ 数据插入完成,共插入", len(allContainer), "条记录") # 关闭连接 mycursor.close() mydb.close() import pandas as pd # 转成 DataFrame df = pd.DataFrame(allContainer, columns=["书名", "链接", "现价", "评论数", "作者", "出版时间", "出版社", "可选状态", "未知"]) # 插入序号列(从 1 开始) df.insert(0, '序号', range(1, len(df) + 1)) # 保存为 Excel 文件 df.to_excel("../data/当当网.xlsx", index=False) print("✅ 数据已成功保存为 Excel 文件!") import jieba import wordcloud # 读取excel 文件到当前代码中 import openpyxl from wordcloud import WordCloud # 获取当前excel 表格对象 wb = openpyxl.load_workbook('../data/当当网.xlsx') # 获取当前表格中的sheet sheet = wb.worksheets[0] # 遍历当前的execl 对象 # min row = 2 代表的是从当前表格中的第二行开始获取 # min col = 3 代表获取第三列 # max col = 3 最大的列是三,之确保我们获取当前第三列 # 定义一个列表用来存储当前获取的所有的数据 data = [] for row in sheet.iter_rows(min_row=2, min_col=2, max_col=5): data.append(row[0].value) # 获取每个单元格中的value值 # print(data) # 对当前的数组中的元素进行分割词组 seg_list = jieba.cut(''.join(data), cut_all=False) # print(type(seg_list)) # # print('/'.join(seg_list)) # 引入当前的字体 作为词云图的渲染字体 fonts = '../data/AlibabaPuHuiTi-2-65-Medium.ttf' wc = WordCloud( # 通过属性对当前的词云图进行赋值 width=1200, # 宽600px height=600, background_color='white', max_font_size=50, min_font_size=10, font_path=fonts ) # 将分隔完成的数据 加载到当前的wc对象中 wc.generate_from_text(''.join(seg_list)) # 保存当前的结果到指定文件夹中 wc.to_file("../data/词云图.png") import numpy as np import pandas as pd # 这些设置有助于调试时查看完整的 DataFrame 数据,适合开发阶段使用 pd.set_option('display.max_columns', None) pd.set_option('display.max_rows', None) pd.set_option('display.width', None) pd.set_option('display.max_colwidth', None) newXml = pd.read_excel("../data/当当网.xlsx") print(newXml.shape) # 查看行数列数 print(newXml.info()) # 查看各列的数据类型及缺失值 # # 检查重复行 duplicates = newXml.duplicated(keep='first') print("重复行数量:", duplicates.sum()) # # 删除重复行 cleaned_data = newXml.drop_duplicates() print("删除重复后数据形状:", cleaned_data.shape) # # 删除含有空值的行 dropna_data = newXml.dropna() print("删除空值后数据形状:", dropna_data.shape) # 或者填充空值 # filled_data = newXml.fillna({"未知": "默认值"}) df = newXml.drop(columns=['未知']) print(df) # filled_data = newXml.fillna({"CPU信息": "未知", "等级": 0}) df.to_excel("../data/new当当.xlsx", index=False) import pandas as pd import numpy as np import pandas as pd from pyecharts import options as opts from pyecharts.charts import Bar, Line, Scatter, Pie, Radar from pyecharts.globals import ThemeType # 读取文件 excel_file = pd.ExcelFile('../data/new当当.xlsx') # 获取指定工作表中的数据 df = excel_file.parse('Sheet1') # 将出版社列转换为字符串类型 df['出版社'] = df['出版社'].astype(str) # 获取出版社和书名列的数据 publishers = df['出版社'].to_numpy() book_names = df['书名'].to_numpy() # 获取唯一的出版社 unique_publishers = np.unique(publishers) # 统计每个出版社的书籍数量 book_counts = np.array([np.sum(publishers == publisher) for publisher in unique_publishers]) # 构建结果 DataFrame result_df = pd.DataFrame({ '出版社': unique_publishers, '书籍数量': book_counts }) print(result_df) # 读取数据 df = pd.read_excel('../data/new当当.xlsx') # 数据预处理 # 转换现价列,提取数字 df['现价'] = df['现价'].str.extract('(\d+\.?\d*)').astype(float) # 转换评论数列,提取数字 df['评论数'] = df['评论数'].str.extract('(\d+)').astype(int) # 转换出版时间列,提取年份 df['出版年份'] = pd.to_datetime(df['出版时间']).dt.year # 图表1:价格分布直方图 hist, bins = pd.cut(df['现价'], bins=20, retbins=True) hist_value = hist.value_counts().sort_index() # 使用 Bar 来模拟直方图 histogram = ( Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width="800px", height="400px")) .add_xaxis([f"{bins[i]:.2f}-{bins[i + 1]:.2f}" for i in range(len(bins) - 1)]) .add_yaxis("书籍数量", hist_value.tolist(), category_gap=0) .set_global_opts( title_opts=opts.TitleOpts(title="价格分布柱状图"), xaxis_opts=opts.AxisOpts(name="价格区间"), yaxis_opts=opts.AxisOpts(name="数量"), ) ) # 图表2:不同出版社出版书籍数量柱状图 publisher_counts = df['出版社'].value_counts() bar_publisher = ( Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width="800px", height="400px")) .add_xaxis(publisher_counts.index.tolist()) .add_yaxis("出版书籍数量", publisher_counts.tolist()) .set_global_opts( title_opts=opts.TitleOpts(title="不同出版社出版书籍数量柱状图"), xaxis_opts=opts.AxisOpts(name="出版社", axislabel_opts={"rotate": 90}), yaxis_opts=opts.AxisOpts(name="出版书籍数量"), ) ) # 图表3:每年出版书籍数量折线图 yearly_counts = df['出版年份'].value_counts().sort_index() line_yearly = ( Line(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width="800px", height="400px")) .add_xaxis(yearly_counts.index.astype(str).tolist()) .add_yaxis("出版书籍数量", yearly_counts.tolist(), is_smooth=True, symbol="circle") .set_global_opts( title_opts=opts.TitleOpts(title="每年出版书籍数量折线图"), xaxis_opts=opts.AxisOpts(name="出版年份"), yaxis_opts=opts.AxisOpts(name="出版书籍数量"), ) ) # 图表4:评论数前五书籍的书名与评论数柱状图 top_5_commented = df.nlargest(5, '评论数') bar_comment = ( Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width="800px", height="400px")) .add_xaxis(top_5_commented['书名'].tolist()) .add_yaxis("评论数", top_5_commented['评论数'].tolist()) .set_global_opts( title_opts=opts.TitleOpts(title="评论数前五书籍的书名与评论数柱状图"), xaxis_opts=opts.AxisOpts(name="书名", axislabel_opts={"rotate": 90}), yaxis_opts=opts.AxisOpts(name="评论数"), ) ) # 图表5:价格与评论数的散点图 # 将现价列转换为字符串类型 df['现价'] = df['现价'].astype(str) # 提取价格数值 df['价格'] = df['现价'].str.extract(r'(\d+\.?\d*)').astype(float) # 检查价格列是否存在缺失值 print(f"价格列缺失值数量: {df['价格'].isna().sum()}") # 删除价格列为缺失值的行 df = df.dropna(subset=['价格']) # 定义价格区间 bins = [0, 50, 100, 150, 200, float('inf')] labels = ['0 - 50', '51 - 100', '101 - 150', '151 - 200', '200以上'] # 划分价格区间并统计数量 df['价格区间'] = pd.cut(df['价格'], bins=bins, labels=labels) price_range_counts = df['价格区间'].value_counts().reset_index(name='数量') # 使用 pyecharts 绘制饼状图 pie = ( Pie() .add( series_name="数量", data_pair=[list(z) for z in zip(price_range_counts['价格区间'], price_range_counts['数量'])], radius=["40%", "75%"], ) .set_global_opts( title_opts=opts.TitleOpts(title="价格区间与数量的饼状图"), legend_opts=opts.LegendOpts(orient="vertical", pos_top="15%", pos_left="2%"), ) .set_series_opts( label_opts=opts.LabelOpts(formatter="{b}: {d}%") ) ) # 将评论数列转换为字符串类型 df['评论数'] = df['评论数'].astype(str) # 提取评论数数值 df['评论数数值'] = df['评论数'].str.extract(r'(\d+\.?\d*)').astype(float) # 找出评论数前五的书籍 top_5_books = df.nlargest(5, '评论数数值', keep='all')[['书名', '评论数数值']] # 定义雷达图的指标 c_schema = [{"name": book_name, "max": top_5_books['评论数数值'].max()} for book_name in top_5_books['书名']] # 准备雷达图的数据 data = [[count for count in top_5_books['评论数数值'].values]] # 创建雷达图对象 ( Radar() .add_schema(schema=c_schema) .add( series_name="评论数", data=data, areastyle_opts=opts.AreaStyleOpts(opacity=0.2) ) .set_global_opts( title_opts=opts.TitleOpts(title="评论数前五的书籍的书名与评论数雷达图"), ) .render("../data/radar_chart_top5_books.html") ) # 统计不同出版社的书籍数量 publisher_book_count = df['出版社'].value_counts().reset_index() publisher_book_count.columns = ['出版社', '书籍数量'] # 选取书籍数量前 10 的出版社 top_10_publisher = publisher_book_count.nlargest(10, '书籍数量') # 创建散点图对象 scatter = ( Scatter() .add_xaxis(top_10_publisher['出版社'].tolist()) .add_yaxis( series_name="书籍数量", y_axis=top_10_publisher['书籍数量'].tolist(), symbol_size=10, label_opts=opts.LabelOpts(is_show=False) ) .set_global_opts( title_opts=opts.TitleOpts(title="不同出版社书籍数量前10的散点图"), xaxis_opts=opts.AxisOpts( name="出版社", type_="category", axislabel_opts=opts.LabelOpts(rotate=45, interval="auto") ), yaxis_opts=opts.AxisOpts(name="书籍数量"), ) ) # 保存图表 histogram.render("../data/price_distribution_histogram.html") bar_publisher.render("../data/publisher_book_count_bar.html") line_yearly.render("../data/yearly_book_count_line.html") bar_comment.render("../data/top_commented_books_bar.html") pie.render("../data/price_range_pie_chart.html") scatter.render("../data/scatter_top10_publisher_book_count.html") from flask import Flask, request, render_template_string, jsonify import requests # import requests # # # 定义一个message的变量,作为会话的容器 # messages = [{"role":"system","content":""}] # # # API KEY # API_KEY = "sk-ec2e933afb424766ba6bce9765960a3a" # # 设置请求头 # header = { # "Content-Type": "application/json", # 告知服务器我们传递的内容的数据类型 # "Authorization": f"Bearer {API_KEY}" # api_key # } # # # 请求地址 # url = "https://api.deepseek.com/chat/completions" # # # 因为要完成多轮对话 所以要有循环 # # 同时因为要完成用户的多次对话请求 # # def DeepSeekChat(userMessage): # # 1. 将用户输入的信息与角色进行拼接 从而变成一个完成的对话 # messages.append( {"role": "user", "content": userMessage}) # # # 2. 请求deepseek 请求过程中将我们携带的多个参数进行传递 # data = { # "model":"deepseek-chat", # "messages":messages, # "stream":False # } # # # 3. 发起请求 # response = requests.post(url, json=data, headers=header) # # # 4. 对response进行处理 # if response.status_code == 200: # # 获取响应内容 # result_json = response.json() # # 处理当前json中的内容 # airesult = result_json['choices'][0]['message']['content'] # # AI返回结果 # print(f"图书商城AI客服:{airesult}") # # 如何实现多轮对话 # # 将回复的内容继续追加到列表中,形成会话的闭合,结合上下文内容 # messages.append({"role": "assistant", "content": airesult}) # else: # print(response.text) # print("请求失败") # # print("图书商城欢迎您") # print("使用exit退出程序") # # while True: # userinput = input("你:") # if userinput == "exit": # print("退出当前程序") # break # else: # # 调用函数完成多次请求的发送 # # 将用户输入的内容作为参数调用函数 完成API的调用 # DeepSeekChat(userinput) 这是我写的代码,需要把爬虫得到的数据与deepseek相结合,使得可以在一个新页面上根据数据与ai对话,请你进行修改

请用中文解释下述代码为什么可以保存EXCEL文件但是显示图片文件保存失败? import cv2 import numpy as np import pandas as pd from PIL import Image import matplotlib.pyplot as plt from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score import os import tkinter as tk from tkinter import simpledialog, filedialog from skimage.feature import hog from skimage import exposure import glob import warnings warnings.filterwarnings('ignore') class SEMCircleDetector: def __init__(self): self.points = [] self.circles = [] # 存储用户选择的圆形 (x, y, radius) self.scale = None # 像素/微米 self.current_image = None self.current_image_path = None self.drawing_circle = False self.center = None self.radius = 0 self.temp_circle = None self.original_image_bgr = None # 保存原始图像的BGR版本 def mouse_callback(self, event, x, y, flags, param): if event == cv2.EVENT_LBUTTONDOWN: if len(self.points) < 6: # 标定点采集阶段 self.points.append((x, y)) cv2.circle(self.current_image, (x, y), 5, (0, 255, 0), -1) if len(self.points) % 2 == 0: idx = len(self.points) // 2 cv2.putText(self.current_image, f'Group {idx}', (x+10, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) cv2.imshow("SEM Image", self.current_image) else: # 圆形选择阶段 self.drawing_circle = True self.center = (x, y) elif event == cv2.EVENT_MOUSEMOVE: if self.drawing_circle and self.center is not None: self.radius = int(np.sqrt((x - self.center[0])**2 + (y - self.center[1])**2)) img_copy = self.current_image.copy() cv2.circle(img_copy, self.center, self.radius, (0, 255, 0), 2) cv2.imshow("SEM Image", img_copy) elif event == cv2.EVENT_LBUTTONUP: if self.drawing_circle and self.center is not None: self.drawing_circle = False # 确保圆形是有效的 if self.radius > 5: self.circles.append((self.center[0], self.center[1], self.radius)) cv2.circle(self.current_image, self.center, self.radius, (255, 0, 0), 2) cv2.imshow("SEM Image", self.current_image) self.center = None self.radius = 0 def calculate_scale(self): if len(self.points) < 6: raise ValueError("需要6个点来计算标尺") distances = [] for i in range(0, 6, 2): x1, y1 = self.points[i] x2, y2 = self.points[i+1] distance = np.sqrt((x2 - x1)**2 + (y2 - y1)**2) distances.append(distance) print(f"第{i//2+1}组距离: {distance:.2f} 像素") # 平均距离(像素)对应1微米 avg_distance = np.mean(distances) self.scale = avg_distance # 像素/微米 print(f"标尺计算: 1微米 = {self.scale:.2f} 像素") return self.scale def extract_features(self, img, center, radius): x, y = center r = int(radius) # 确保不越界 x1, y1 = max(0, x - r), max(0, y - r) x2, y2 = min(img.shape[1], x + r), min(img.shape[0], y + r) if x1 >= x2 or y1 >= y2: return None roi = img[y1:y2, x1:x2] if roi.size == 0: return None # 调整大小以便提取特征 roi = cv2.resize(roi, (64, 64)) gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) # 提取HOG特征 try: fd, hog_image = hog(gray, orientations=8, pixels_per_cell=(16, 16), cells_per_block=(1, 1), visualize=True) except: # 如果HOG特征提取失败,使用零向量替代 fd = np.zeros(128) # HOG特征向量的默认长度 # 提取其他特征 mean_val = np.mean(gray) std_val = np.std(gray) entropy_val = self.calculate_entropy(gray) # 组合所有特征 features = np.concatenate([fd, [mean_val, std_val, entropy_val]]) return features def calculate_entropy(self, image): # 计算图像熵 hist = np.histogram(image, bins=256, range=(0, 255))[0] hist = hist / hist.sum() entropy = -np.sum(hist * np.log2(hist + 1e-10)) return entropy def train_classifier(self, features, labels): X_train, X_test, y_train, y_test = train_test_split( features, labels, test_size=0.2, random_state=42) clf = RandomForestClassifier(n_estimators=100, random_state=42) clf.fit(X_train, y_train) y_pred = clf.predict(X_test) accuracy = accuracy_score(y_test, y_pred) print(f"Classifier accuracy: {accuracy:.2f}") return clf def process_image(self, image_path): self.current_image_path = image_path # 使用PIL读取图像并转换为OpenCV格式 pil_image = Image.open(image_path).convert('L') self.original_image = np.array(pil_image) self.original_image_bgr = cv2.cvtColor(self.original_image, cv2.COLOR_GRAY2BGR) self.current_image = self.original_image_bgr.copy() # 重置状态 self.points = [] self.circles = [] # 创建可调整大小的窗口 cv2.namedWindow("SEM Image", cv2.WINDOW_NORMAL) cv2.resizeWindow("SEM Image", 1000, 800) # 设置初始窗口大小 cv2.setMouseCallback("SEM Image", self.mouse_callback) print("请点击6个点来标定比例尺(每两个点之间的距离为1微米)") print("点击完成后,按任意键继续") while True: cv2.imshow("SEM Image", self.current_image) key = cv2.waitKey(1) & 0xFF if len(self.points) >= 6: break # 计算比例尺 self.calculate_scale() print(f"标尺计算完成: 1微米 = {self.scale:.2f} 像素") print("现在请选择至少50个圆形(点击圆心并拖动确定半径)") print("选择完成后,按 'q' 键退出") while True: cv2.imshow("SEM Image", self.current_image) key = cv2.waitKey(1) & 0xFF if key == ord('q'): break cv2.destroyAllWindows() # 保存标记后的图像 base_name = os.path.basename(image_path) name, ext = os.path.splitext(base_name) output_dir = os.path.join(os.path.dirname(image_path), "processed") os.makedirs(output_dir, exist_ok=True) # 保存用户框选的图片 - 使用PNG格式 user_marked_image = self.original_image_bgr.copy() for cx, cy, r in self.circles: cv2.circle(user_marked_image, (cx, cy), r, (255, 0, 0), 2) output_image_path = os.path.join(output_dir, f"{name}_user_marked.png") success = cv2.imwrite(output_image_path, user_marked_image) if success: print(f"用户框选的图像已保存: {output_image_path}") else: print(f"错误: 无法保存用户框选的图像到 {output_image_path}") print(f"请检查文件路径和权限") # 保存统计信息 self.save_statistics(image_path, output_dir) # 使用机器学习检测剩余圆形 self.detect_remaining_circles(output_dir) return self.circles def save_statistics(self, image_path, output_dir): if not self.circles: print("没有选择圆形") return # 转换为微米单位 radii_um = [r / self.scale for _, _, r in self.circles] # 创建DataFrame df = pd.DataFrame({ 'Center_X': [c[0] for c in self.circles], 'Center_Y': [c[1] for c in self.circles], 'Radius_pixels': [c[2] for c in self.circles], 'Radius_um': radii_um }) # 保存到CSV base_name = os.path.basename(image_path) name, ext = os.path.splitext(base_name) output_csv_path = os.path.join(output_dir, f"{name}_user_circles.csv") df.to_csv(output_csv_path, index=False) print(f"用户选择的圆形统计信息已保存: {output_csv_path}") # 显示基本统计信息 print(f"选择了 {len(radii_um)} 个圆形") print(f"平均半径: {np.mean(radii_um):.4f} 微米") print(f"半径标准差: {np.std(radii_um):.4f} 微米") print(f"最小半径: {np.min(radii_um):.4f} 微米") print(f"最大半径: {np.max(radii_um):.4f} 微米") def detect_remaining_circles(self, output_dir): print(f"已选择的圆形数量: {len(self.circles)}") if not self.circles or len(self.circles) < 10: print("选择的圆形数量不足,无法进行机器学习检测") return print("使用机器学习方法检测剩余圆形...") # 准备训练数据 features = [] labels = [] # 1表示圆形,0表示非圆形 # 正样本(已选择的圆形) valid_circles = 0 for cx, cy, r in self.circles: feat = self.extract_features(self.original_image_bgr, (cx, cy), r) if feat is not None: features.append(feat) labels.append(1) valid_circles += 1 print(f"有效正样本数量: {valid_circles}") # 负样本(随机非圆形区域) img_h, img_w = self.original_image_bgr.shape[:2] num_negative = valid_circles # 使用有效正样本数量 negative_count = 0 max_attempts = num_negative * 10 # 最大尝试次数 for attempt in range(max_attempts): if negative_count >= num_negative: break x = np.random.randint(50, img_w - 50) y = np.random.randint(50, img_h - 50) r = np.random.randint(10, 30) # 确保这个区域不包含任何已选择的圆形 is_valid = True for cx, cy, cr in self.circles: if np.sqrt((x - cx)**2 + (y - cy)**2) < cr + r: is_valid = False break if is_valid: feat = self.extract_features(self.original_image_bgr, (x, y), r) if feat is not None: features.append(feat) labels.append(0) negative_count += 1 print(f"负样本数量: {negative_count}") # 训练分类器 features = np.array(features) labels = np.array(labels) if len(np.unique(labels)) < 2: print("正负样本不平衡,无法训练分类器") return clf = self.train_classifier(features, labels) # 在全图像中检测圆形 detected_circles = [] step = 10 # 扫描步长 # 创建用于机器学习检测的图像副本 ml_image = self.original_image_bgr.copy() # 先绘制用户选择的圆形(蓝色) for cx, cy, r in self.circles: cv2.circle(ml_image, (cx, cy), r, (255, 0, 0), 2) # 再绘制机器学习检测的圆形(红色) for y in range(30, img_h - 30, step): for x in range(30, img_w - 30, step): # 检查这个位置是否已经有圆形 is_duplicate = False for cx, cy, cr in self.circles + detected_circles: if np.sqrt((x - cx)**2 + (y - cy)**2) < cr + 10: is_duplicate = True break if not is_duplicate: # 尝试不同的半径 for r in range(10, 50, 5): feat = self.extract_features(self.original_image_bgr, (x, y), r) if feat is not None: proba = clf.predict_proba([feat])[0] if proba[1] > 0.7: # 圆形概率阈值 detected_circles.append((x, y, r)) cv2.circle(ml_image, (x, y), r, (0, 0, 255), 2) break # 保存新检测的圆形 if detected_circles: # 保存机器学习检测结果的图像 - 使用PNG格式 base_name = os.path.basename(self.current_image_path) name, ext = os.path.splitext(base_name) output_image_path = os.path.join(output_dir, f"{name}_ml_detected.png") success = cv2.imwrite(output_image_path, ml_image) if success: print(f"机器学习检测结果图像已保存: {output_image_path}") else: print(f"错误: 无法保存机器学习检测结果图像到 {output_image_path}") print(f"请检查文件路径和权限") # 保存新的统计信息 ml_csv_path = os.path.join(output_dir, f"{name}_ml_circles.csv") radii_um = [r / self.scale for _, _, r in detected_circles] ml_df = pd.DataFrame({ 'Center_X': [c[0] for c in detected_circles], 'Center_Y': [c[1] for c in detected_circles], 'Radius_pixels': [c[2] for c in detected_circles], 'Radius_um': radii_um }) ml_df.to_csv(ml_csv_path, index=False) print(f"机器学习检测的圆形统计信息已保存: {ml_csv_path}") # 保存包含用户选择和机器学习检测的所有圆形 all_circles = self.circles + detected_circles all_csv_path = os.path.join(output_dir, f"{name}_all_circles.csv") all_radii_um = [r / self.scale for _, _, r in all_circles] all_df = pd.DataFrame({ 'Center_X': [c[0] for c in all_circles], 'Center_Y': [c[1] for c in all_circles], 'Radius_pixels': [c[2] for c in all_circles], 'Radius_um': all_radii_um, 'Source': ['User'] * len(self.circles) + ['ML'] * len(detected_circles) }) all_df.to_csv(all_csv_path, index=False) print(f"所有圆形统计信息已保存: {all_csv_path}") print(f"通过机器学习新检测到 {len(detected_circles)} 个圆形") # 打印一些机器学习检测的圆形半径信息用于调试 print("机器学习检测的部分圆形半径(微米):") for i, r_um in enumerate(radii_um[:10]): print(f"{i+1}. {r_um:.6f}") else: print("未通过机器学习检测到新的圆形") def get_image_files(folder_path): """获取文件夹中的所有图像文件""" extensions = ['*.png', '*.jpg', '*.jpeg', '*.tif', '*.tiff', '*.bmp', '*.PNG', '*.JPG', '*.JPEG', '*.TIF', '*.TIFF', '*.BMP'] image_files = [] for ext in extensions: pattern = os.path.join(folder_path, ext) image_files.extend(glob.glob(pattern)) return sorted(image_files) def main(): # 直接输入文件夹路径 folder_path = input("请输入包含SEM图像的文件夹路径: ").strip().strip('"') if not os.path.exists(folder_path): print("文件夹路径不存在,请检查后重试") return # 获取文件夹中的所有图像文件 image_paths = get_image_files(folder_path) if not image_paths: print("文件夹中没有找到图像文件") return print(f"找到 {len(image_paths)} 张图像文件:") for i, path in enumerate(image_paths): print(f"{i+1}. {os.path.basename(path)}") # 创建检测器并处理每张图像 detector = SEMCircleDetector() all_results = [] for i, path in enumerate(image_paths): print(f"\n处理图像 {i+1}/{len(image_paths)}: {os.path.basename(path)}") circles = detector.process_image(path) all_results.append((path, circles)) print("\n所有图像处理完成!") if __name__ == "__main__": main()

import streamlit as st import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from pyspark.sql import SparkSession from pyspark.ml.feature import VectorAssembler, StringIndexer, OneHotEncoder from pyspark.ml import Pipeline from pyspark.ml.classification import LogisticRegression, DecisionTreeClassifier, RandomForestClassifier from pyspark.ml.evaluation import BinaryClassificationEvaluator, MulticlassClassificationEvaluator import joblib import os import time import warnings from io import BytesIO import platform from pathlib import Path def safe_path(path): “”“处理Windows长路径问题”“” if platform.system() == ‘Windows’: try: import ntpath return ntpath.realpath(path) except: return str(Path(path).resolve()) return path 忽略警告 warnings.filterwarnings(“ignore”) 设置中文字体 plt.rcParams[‘font.sans-serif’] = [‘SimHei’] plt.rcParams[‘axes.unicode_minus’] = False 页面设置 st.set_page_config( page_title=“精准营销系统”, page_icon=“📊”, layout=“wide”, initial_sidebar_state=“expanded” ) 自定义CSS样式 st.markdown(“”" <style> .stApp { background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); font-family: 'Helvetica Neue', Arial, sans-serif; } .header { background: linear-gradient(90deg, #1a237e 0%, #283593 100%); color: white; padding: 1.5rem; border-radius: 0.75rem; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin-bottom: 2rem; } .card { background: white; border-radius: 0.75rem; padding: 1rem; margin-bottom: 1.5rem; box-shadow: 0 4px 12px rgba(0,0,0,0.08); transition: transform 0.3s ease; } .card:hover { transform: translateY(-5px); box-shadow: 0 6px 16px rgba(0,0,0,0.12); } .stButton button { background: linear-gradient(90deg, #3949ab 0%, #1a237e 100%) !important; color: white !important; border: none !important; border-radius: 0.5rem; padding: 0.75rem 1.5rem; font-size: 1rem; font-weight: 600; transition: all 0.3s ease; width: 100%; } .stButton button:hover { transform: scale(1.05); box-shadow: 0 4px 8px rgba(57, 73, 171, 0.4); } .feature-box { background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%); border-radius: 0.75rem; padding: 1.5rem; margin-bottom: 1.5rem; } .result-box { background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%); border-radius: 0.75rem; padding: 1.5rem; margin-top: 1.5rem; } .model-box { background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%); border-radius: 0.75rem; padding: 1.5rem; margin-top: 1.5rem; } .stProgress > div > div > div { background: linear-gradient(90deg, #2ecc71 0%, #27ae60 100%) !important; } .metric-card { background: white; border-radius: 0.75rem; padding: 1rem; text-align: center; box-shadow: 0 4px 8px rgba(0,0,0,0.06); } .metric-value { font-size: 1.8rem; font-weight: 700; color: #1a237e; } .metric-label { font-size: 0.9rem; color: #5c6bc0; margin-top: 0.5rem; } .highlight { background: linear-gradient(90deg, #ffeb3b 0%, #fbc02d 100%); padding: 0.2rem 0.5rem; border-radius: 0.25rem; font-weight: 600; } .stDataFrame { border-radius: 0.75rem; box-shadow: 0 4px 8px rgba(0,0,0,0.06); } .convert-high { background-color: #c8e6c9 !important; color: #388e3c !important; font-weight: 700; } .convert-low { background-color: #ffcdd2 !important; color: #c62828 !important; font-weight: 600; } </style> “”", unsafe_allow_html=True) 创建Spark会话 def create_spark_session(): return SparkSession.builder .appName(“TelecomPrecisionMarketing”) .config(“spark.driver.memory”, “4g”) .config(“spark.executor.memory”, “4g”) .getOrCreate() 数据预处理函数 - 修改后 def preprocess_data(df): “”" 数据预处理函数 参数: df: 原始数据 (DataFrame) 返回: 预处理后的数据 (DataFrame) “”" # 1. 选择关键特征 - 使用实际存在的列名 available_features = [col for col in df.columns if col in [ ‘AGE’, ‘GENDER’, ‘ONLINE_DAY’, ‘TERM_CNT’, ‘IF_YHTS’, ‘MKT_STAR_GRADE_NAME’, ‘PROM_AMT_MONTH’, ‘is_rh_next’ # 目标变量 ]] # 确保目标变量存在 if 'is_rh_next' not in available_features: st.error("错误:数据集中缺少目标变量 'is_rh_next'") return df # 只保留需要的列 df = df[available_features].copy() # 2. 处理缺失值 # 数值特征用均值填充 numeric_cols = ['AGE', 'ONLINE_DAY', 'TERM_CNT', 'PROM_AMT_MONTH'] for col in numeric_cols: if col in df.columns: mean_val = df[col].mean() df[col].fillna(mean_val, inplace=True) # 分类特征用众数填充 categorical_cols = ['GENDER', 'MKT_STAR_GRADE_NAME', 'IF_YHTS'] for col in categorical_cols: if col in df.columns: mode_val = df[col].mode()[0] df[col].fillna(mode_val, inplace=True) # 3. 异常值处理(使用IQR方法) def handle_outliers(series): Q1 = series.quantile(0.25) Q3 = series.quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR return series.clip(lower_bound, upper_bound) for col in numeric_cols: if col in df.columns: df[col] = handle_outliers(df[col]) return df 标题区域 st.markdown(“”" 精准营销系统 基于机器学习的单宽转融预测 """, unsafe_allow_html=True) 页面布局 col1, col2 = st.columns([1, 1.5]) 左侧区域 - 图片和简介 with col1: st.markdown(“”" 📱 智能营销系统 预测单宽带用户转化为融合套餐用户的可能性 “”", unsafe_allow_html=True) # 使用在线图片作为占位符 st.image("https://images.unsplash.com/photo-1551836022-d5d88e9218df?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1200&q=80", caption="精准营销系统示意图", width=600) st.markdown(""" 📈 系统功能 用户转化可能性预测 高精度机器学习模型 可视化数据分析 精准营销策略制定 """, unsafe_allow_html=True) 右侧区域 - 功能选择 with col2: st.markdown(“”" 📋 请选择操作类型 您可以选择数据分析或使用模型进行预测 “”", unsafe_allow_html=True) # 功能选择 option = st.radio("", ["📊 数据分析 - 探索数据并训练模型", "🔍 预测分析 - 预测用户转化可能性"], index=0, label_visibility="hidden") # 数据分析部分 if "数据分析" in option: st.markdown(""" 数据分析与模型训练 上传数据并训练预测模型 </极客时间> """, unsafe_allow_html=True) # 上传训练数据 train_file = st.file_uploader("上传数据集 (CSV格式, GBK编码)", type=["csv"]) if train_file is not None: try: # 读取数据 train_data = pd.read_csv(train_file, encoding='GBK') # 显示数据预览 with st.expander("数据预览", expanded=True): st.dataframe(train_data.head()) col1, col2 = st.columns(2) col1.metric("总样本数", train_data.shape[0]) col2.metric("特征数量", train_data.shape[1] - 1) # 数据预处理 st.subheader("数据预处理") with st.spinner("数据预处理中..."): processed_data = preprocess_data(train_data) st.success("✅ 数据预处理完成") # 可视化数据分布 st.subheader("数据分布分析") # 目标变量分布 st.markdown("**目标变量分布 (is_rh_next)**") fig, ax = plt.subplots(figsize=(8, 5)) sns.countplot(x='is_rh_next', data=processed_data, palette='viridis') plt.title('用户转化分布 (0:未转化, 1:转化)') plt.xlabel('是否转化') plt.ylabel('用户数量') st.pyplot(fig) # 数值特征分布 st.markdown("**数值特征分布**") numeric_cols = ['AGE', 'ONLINE_DAY', 'TERM_CNT', 'PROM_AMT_MONTH'] # 动态计算子图布局 num_features = len(numeric_cols) if num_features > 0: ncols = 2 nrows = (num_features + ncols - 1) // ncols # 向上取整 fig, axes = plt.subplots(nrows, ncols, figsize=(14, 4*nrows)) # 将axes展平为一维数组 if nrows > 1 or ncols > 1: axes = axes.flatten() else: axes = [axes] # 单个子图时确保axes是列表 for i, col in enumerate(numeric_cols): if col in processed_data.columns and i < len(axes): sns.histplot(processed_data[col], kde=True, ax=axes[i], color='skyblue') axes[i].set_title(f'{col}分布') axes[i].set_xlabel('') # 隐藏多余的子图 for j in range(i+1, len(axes)): axes[j].set_visible(False) plt.tight_layout() st.pyplot(fig) else: st.warning("没有可用的数值特征") # 特征相关性分析 st.markdown("**特征相关性热力图**") corr_cols = numeric_cols + ['is_rh_next'] if len(corr_cols) > 1: corr_data = processed_data[corr_cols].corr() fig, ax = plt.subplots(figsize=(12, 8)) sns.heatmap(corr_data, annot=True, fmt=".2f", cmap='coolwarm', ax=ax) plt.title('特征相关性热力图') st.pyplot(fig) else: st.warning("特征不足,无法生成相关性热力图") # 模型训练 st.subheader("模型训练") # 训练参数设置 col1, col2 = st.columns(2) test_size = col1.slider("测试集比例", 0.1, 0.4, 0.2, 0.05) random_state = col2.number_input("随机种子", 0, 100, 42) # 开始训练按钮 if st.button("开始训练模型", use_container_width=True): with st.spinner("模型训练中,请稍候..."): # 创建Spark会话 spark = create_spark_session() # 将Pandas DataFrame转换为Spark DataFrame spark_df = spark.createDataFrame(processed_data) # 划分训练集和测试集 train_df, test_df = spark_df.randomSplit([1.0 - test_size, test_size], seed=random_state) # 特征工程 # 分类特征编码 categorical_cols = ['GENDER', 'MKT_STAR_GRADE_NAME', 'IF_YHTS'] # 只处理存在的分类特征 existing_cat_cols = [col for col in categorical_cols if col in processed_data.columns] indexers = [StringIndexer(inputCol=col, outputCol=col+"_index") for col in existing_cat_cols] encoders = [OneHotEncoder(inputCol=col+"_index", outputCol=col+"_encoded") for col in existing_cat_cols] # 数值特征 numeric_cols = ['AGE', 'ONLINE_DAY', 'TERM_CNT', 'PROM_AMT_MONTH'] # 组合所有特征 feature_cols = numeric_cols + [col+"_encoded" for col in existing_cat_cols] assembler = VectorAssembler(inputCols=feature_cols, outputCol="features") # 目标变量索引 label_indexer = StringIndexer(inputCol="is_rh_next", outputCol="label") # 构建模型 lr = LogisticRegression(featuresCol="features", labelCol="label") dt = DecisionTreeClassifier(featuresCol="features", labelCol="label") rf = RandomForestClassifier(featuresCol="features", labelCol="label") # 创建管道 pipeline_lr = Pipeline(stages=indexers + encoders + [assembler, label_indexer, lr]) pipeline_dt = Pipeline(stages=indexers + encoders + [assembler, label_indexer, dt]) pipeline_rf = Pipeline(stages=indexers + encoders + [assembler, label_indexer, rf]) # 训练模型 model_lr = pipeline_lr.fit(train_df) model_dt = pipeline_dt.fit(train_df) model_rf = pipeline_rf.fit(train_df) # 评估模型 evaluator_auc = BinaryClassificationEvaluator(labelCol="label", rawPredictionCol="rawPrediction") evaluator_acc = MulticlassClassificationEvaluator(labelCol="label", predictionCol="prediction", metricName="accuracy") evaluator_f1 = MulticlassClassificationEvaluator(labelCol="label", predictionCol="prediction", metricName="f1") def evaluate_model(model, data): predictions = model.transform(data) auc = evaluator_auc.evaluate(predictions) acc = evaluator_acc.evaluate(predictions) f1 = evaluator_f1.evaluate(predictions) return {"AUC": auc, "Accuracy": acc, "F1": f1} results = { "Logistic Regression": evaluate_model(model_lr, test_df), "Decision Tree": evaluate_model(model_dt, test_df), "Random Forest": evaluate_model(model_rf, test_df) } # 保存结果 st.session_state.model_results = results st.session_state.best_model = model_rf # 默认使用随机森林作为最佳模型 st.session_state.spark = spark st.success("🎉 模型训练完成!") # 显示模型性能 st.subheader("模型性能评估") # 转换为DataFrame展示 results_df = pd.DataFrame(results).T st.dataframe(results_df.style.format("{:.4f}").background_gradient(cmap='Blues')) # 可视化比较 fig, ax = plt.subplots(figsize=(10, 6)) results_df.plot(kind='bar', ax=ax) plt.title('模型性能比较') plt.ylabel('分数') plt.xticks(rotation=15) plt.legend(loc='upper right') st.pyplot(fig) # 特征重要性(随机森林) st.subheader("随机森林特征重要性") rf_model = model_rf.stages[-1] feature_importances = rf_model.featureImportances.toArray() feature_names = numeric_cols + [f"{col}_encoded" for col in existing_cat_cols] importance_df = pd.DataFrame({ "Feature": feature_names, "Importance": feature_importances }).sort_values("Importance", ascending=False).head(10) fig, ax = plt.subplots(figsize=(10, 6)) sns.barplot(x="Importance", y="Feature", data=importance_df, palette="viridis", ax=ax) plt.title('Top 10 重要特征') st.pyplot(fig) # 保存模型 model_path = "best_model" model_rf.write().overwrite().save(model_path) st.session_state.model_path = model_path except Exception as e: st.error(f"数据处理错误: {str(e)}") # 预测分析部分 else: st.markdown(""" 用户转化预测 预测单宽带用户转化为融合套餐的可能性 """, unsafe_allow_html=True) # 上传预测数据 predict_file = st.file_uploader("上传预测数据 (CSV格式, GBK编码)", type=["csv"]) if predict_file is not None: try: # 读取数据 predict_data = pd.read_csv(predict_file, encoding='GBK') # 显示数据预览 with st.expander("数据预览", expanded=True): st.dataframe(predict_data.head()) # 检查是否有模型 if "model_path" not in st.session_state or not os.path.exists(st.session_state.model_path): st.warning("⚠️ 未找到训练好的模型,请先训练模型") st.stop() # 开始预测按钮 if st.button("开始预测", use_container_width=True): with st.spinner("预测进行中,请稍候..."): # 数据预处理 processed_data = preprocess_data(predict_data) # 创建Spark会话 if "spark" not in st.session_state: spark = create_spark_session() st.session_state.spark = spark else: spark = st.session_state.spark # 将Pandas DataFrame转换为Spark DataFrame spark_df = spark.createDataFrame(processed_data) # 加载模型 best_model = st.session_state.best_model # 生成预测结果 predictions = best_model.transform(spark_df) # 提取预测结果 predictions_df = predictions.select( "CCUST_ROW_ID", "probability", "prediction" ).toPandas() # 解析概率值 predictions_df['转化概率'] = predictions_df['probability'].apply(lambda x: float(x[1])) predictions_df['预测结果'] = predictions_df['prediction'].apply(lambda x: "可能转化" if x == 1.0 else "可能不转化") # 添加转化可能性等级 predictions_df['转化可能性'] = pd.cut( predictions_df['转化概率'], bins=[0, 0.3, 0.7, 1], labels=["低可能性", "中可能性", "高可能性"] ) # 保存结果 st.session_state.prediction_results = predictions_df st.success("✅ 预测完成!") except Exception as e: st.error(f"预测错误: {str(e)}") # 显示预测结果 if "prediction_results" in st.session_state: st.markdown(""" 预测结果 用户转化可能性评估报告 """, unsafe_allow_html=True) result_df = st.session_state.prediction_results # 转化可能性分布 st.subheader("转化可能性分布概览") col1, col2, col3 = st.columns(3) high_conv = (result_df["转化可能性"] == "高可能性").sum() med_conv = (result_df["转化可能性"] == "中可能性").sum() low_conv = (result_df["转化可能性"] == "低可能性").sum() col1.markdown(f""" {high_conv} 高可能性用户 """, unsafe_allow_html=True) col2.markdown(f""" {med_conv} 中可能性用户 """, unsafe_allow_html=True) col3.markdown(f""" {low_conv} 低可能性用户 """, unsafe_allow_html=True) # 转化可能性分布图 fig, ax = plt.subplots(figsize=(8, 5)) conv_counts = result_df["转化可能性"].value_counts() conv_counts.plot(kind='bar', color=['#4CAF50', '#FFC107', '#F44336'], ax=ax) plt.title('用户转化可能性分布') plt.xlabel('可能性等级') plt.ylabel('用户数量') st.pyplot(fig) # 详细预测结果 st.subheader("详细预测结果") # 样式函数 def color_convert(val): if val == "高可能性": return "background-color: #c8e6c9; color: #388e3c;" elif val == "中可能性": return "background-color: #fff9c4; color: #f57f17;" else: return "background-color: #ffcdd2; color: #c62828;" # 格式化显示 display_df = result_df[["CCUST_ROW_ID", "转化概率", "预测结果", "转化可能性"]] styled_df = display_df.style.format({ "转化概率": "{:.2%}" }).applymap(color_convert, subset=["转化可能性"]) st.dataframe(styled_df, height=400) # 下载结果 csv = display_df.to_csv(index=False).encode("utf-8") st.download_button( label="下载预测结果", data=csv, file_name="用户转化预测结果.csv", mime="text/csv", use_container_width=True ) 页脚 st.markdown(“—”) st.markdown(“”" © 2023 精准营销系统 | 基于Spark和Streamlit开发 """, unsafe_allow_html=True) 将上述所给代码,不使用spark,仿照如下所给代码,完成算法和模型调优等操作 import streamlit as st import pandas as pd import numpy as np import joblib import os import time import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib as mpl import matplotlib.font_manager as fm import seaborn as sns from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score, roc_auc_score, confusion_matrix from sklearn.preprocessing import StandardScaler from imblearn.over_sampling import SMOTE from sklearn.impute import SimpleImputer import warnings warnings.filterwarnings("ignore") plt.rcParams[‘font.sans-serif’] = [‘SimHei’] plt.rcParams[‘axes.unicode_minus’] = False # 正确显示负号 页面设置 st.set_page_config( page_title=“风控违约预测系统”, page_icon=“📊”, layout=“wide”, initial_sidebar_state=“expanded” ) 自定义CSS样式 st.markdown(“”" <style> .stApp { background: linear-gradient(135deg, #f5f7fa 0%, #e4edf5 100%); font-family: 'Helvetica Neue', Arial, sans-serif; } .header { background: linear-gradient(90deg, #2c3e50 0%, #4a6491 100%); color: white; padding: 1.5rem; border-radius: 0.75rem; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin-bottom: 2rem; } .card { background: white; border-radius: 0.75rem; padding: 1.5rem; margin-bottom: 1.5rem; box-shadow: 0 4px 12px rgba(0,0,0,0.08); transition: transform 0.3s ease; } .card:hover { transform: translateY(-5px); box-shadow: 0 6px 16px rgba(0,0,0,0.12); } .stButton button { background: linear-gradient(90deg, #3498db 0%, #1a5276 100%) !important; color: white !important; border: none !important; border-radius: 0.5rem; padding: 0.75rem 1.5rem; font-size: 1rem; font-weight: 600; transition: all 0.3s ease; width: 100%; } .stButton button:hover { transform: scale(1.05); box-shadow: 0 4px 8px rgba(52, 152, 219, 0.4); } .feature-box { background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%); border-radius: 0.75rem; padding: 1.5rem; margin-bottom: 1.5rem; } .result-box { background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%); border-radius: 0.75rem; padding: 1.5rem; margin-top: 1.5rem; } .model-box { background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%); border-radius: 0.75rem; padding: 1.5rem; margin-top: 1.5rem; } .stProgress > div > div > div { background: linear-gradient(90deg, #2ecc71 0%, #27ae60 100%) !important; } .metric-card { background: white; border-radius: 0.75rem; padding: 1rem; text-align: center; box-shadow: 0 4px 8px rgba(0,0,0,0.06); } .metric-value { font-size: 1.8rem; font-weight: 700; color: #2c3e50; } .metric-label { font-size: 0.9rem; color: #7f8c8d; margin-top: 0.5rem; } .highlight { background: linear-gradient(90deg, #ffeb3b 0%, #fbc02d 100%); padding: 0.2rem 0.5rem; border-radius: 0.25rem; font-weight: 600; } .stDataFrame { border-radius: 0.75rem; box-shadow: 0 4px 8px rgba(0,0,0,0.06); } .risk-high { background-color: #ffcdd2 !important; color: #c62828 !important; font-weight: 700; } .risk-medium { background-color: #fff9c4 !important; color: #f57f17 !important; font-weight: 600; } .risk-low { background-color: #c8e6c9 !important; color: #388e3c !important; } </style> “”", unsafe_allow_html=True) def preprocess_loan_data(data_old): “”" 训练时数据预处理函数,返回处理后的数据和推理时需要的参数 参数: data_old: 原始训练数据 (DataFrame) 返回: processed_data: 预处理后的训练数据 (DataFrame) preprocessor_params: 推理时需要的预处理参数 (dict) """ # 1. 创建原始数据副本 loan_data = data_old.copy() # 2. 保存要删除的列列表 drop_list = ['id','member_id', 'term', 'pymnt_plan', 'initial_list_status', 'sub_grade', 'emp_title', 'issue_d', 'title', 'zip_code', 'addr_state', 'earliest_cr_line', 'last_pymnt_d', 'last_credit_pull_d', 'url','desc','next_pymnt_d'] loan_data.drop([col for col in drop_list if col in loan_data.columns], axis=1, inplace=True, errors='ignore') # 3. 删除缺失值超过90%的列 #todo 自己补齐删除代码 missing_ratio = loan_data.isnull().sum() / len(loan_data) loan_data.drop(missing_ratio[missing_ratio > 0.9].index, axis=1, inplace=True, errors='ignore') # 4. 删除值全部相同的列 #todo 自己补齐删除代码 constant_cols = loan_data.columns[loan_data.nunique() <= 1] loan_data.drop(constant_cols, axis=1, inplace=True, errors='ignore') # 5. 处理特殊数值列 loans = loan_data # 修正变量名 loans["int_rate"] = loans["int_rate"].astype(str).str.rstrip('%').astype("float") loans["revol_util"] = loans["revol_util"].astype(str).str.rstrip('%').astype("float") # 6. 缺失值处理 ## 识别分类列和数值列 objectColumns = loans.select_dtypes(include=["object"]).columns.tolist() numColumns = loans.select_dtypes(include=[np.number]).columns.tolist() ## 保存分类列的列名 categorical_columns = objectColumns.copy() ## 填充分类变量缺失值 loans[objectColumns] = loans[objectColumns].fillna("Unknown") ## 填充数值变量缺失值并保存均值 imr = SimpleImputer(missing_values=np.nan, strategy="mean") loans[numColumns] = imr.fit_transform(loans[numColumns]) # 保存数值列的均值 numerical_means = {col: imr.statistics_[i] for i, col in enumerate(numColumns)} # 8. 特征衍生 loans["installment_feat"] = loans["installment"] / ((loans["annual_inc"] + 1) / 12) # 9. 目标变量编码 status_mapping = { "Current": 0, "Issued": 0, "Fully Paid": 0, "In Grace Period": 1, "Late (31-120 days)": 1, "Late (16-30 days)": 1, "Charged Off": 1, "Does not meet the credit policy. Status:Charged Off": 1, "Does not meet the credit policy. Status:Fully Paid": 0, "Default": 0 } loans["loan_status"] = loans["loan_status"].map(status_mapping) # 10. 有序特征映射 mapping_dict = { "emp_length": { "10+ years": 10, "9 years": 9, "8 years": 8, "7 years": 7, "6 years": 6, "5 years": 5, "4 years": 4, "3 years": 3, "2 years": 2, "1 year": 1, "< 1 year": 0, "Unknown": 0 }, "grade": { "A": 1, "B": 2, "C": 3, "D": 4, "E": 5, "F": 6, "G": 7 } } loans = loans.replace(mapping_dict) # 11. One-hot编码 n_columns = ["home_ownership", "verification_status", "purpose", "application_type"] dummy_df = pd.get_dummies(loans[n_columns], drop_first=False) loans = pd.concat([loans, dummy_df], axis=1) loans.drop(n_columns, axis=1, inplace=True) # 保存One-hot编码后的列名 onehot_columns = n_columns onehot_encoder_columns = dummy_df.columns.tolist() # 12. 特征缩放 # 识别需要缩放的数值列 numeric_cols = loans.select_dtypes(include=["int", "float"]).columns.tolist() if 'loan_status' in numeric_cols: numeric_cols.remove('loan_status') # 创建并拟合缩放器 sc = StandardScaler() if numeric_cols: loans[numeric_cols] = sc.fit_transform(loans[numeric_cols]) # 保存缩放列名 scaled_columns = numeric_cols # 13. 保存最终列结构(在SMOTE之前) #final_columns = loans.columns.tolist().remove('loan_status') final_columns = loans.columns[loans.columns != 'loan_status'].tolist() # 14. 处理不平衡数据(SMOTE过采样) X = loans.drop("loan_status", axis=1) y = loans["loan_status"] os = SMOTE(random_state=42) X_res, y_res = os.fit_resample(X, y) # 15. 合并为最终DataFrame processed_data = pd.concat([X_res, y_res], axis=1) processed_data.columns = list(X.columns) + ["loan_status"] # 16. 创建推理时需要的参数字典 preprocessor_params = { # 1. 删除的列 'drop_list': drop_list, # 2. 分类列缺失值填充 'categorical_columns': categorical_columns, # 3. 数值列填充均值 'numerical_means': numerical_means, # 4. 有序特征映射 'mapping_dict': mapping_dict, # 5. One-hot配置 'onehot_columns': onehot_columns, 'onehot_encoder_columns': onehot_encoder_columns, # 6. 缩放器及缩放列 'scaler': sc, # 已拟合的StandardScaler实例 'scaled_columns': scaled_columns, # 7. 最终列结构(训练后的列顺序) 'final_columns': final_columns } return processed_data, preprocessor_params def preprocess_loan_data_inference(data_old, preprocessor_params): “”" 推理时数据处理函数 参数: data_old: 原始推理数据 (DataFrame) preprocessor_params: 从训练过程保存的预处理参数 (dict) 返回: processed_data: 预处理后的推理数据 (DataFrame) """ # 1. 复制数据避免污染原始数据 loanss = data_old.copy() # 2. 删除训练时确定的列 drop_list = preprocessor_params['drop_list'] loans = loanss.drop(columns=[col for col in drop_list if col in loanss.columns], axis=1, errors='ignore') # 3. 处理特殊数值列(百分比转换) if 'int_rate' in loans: loans["int_rate"] = loans["int_rate"].astype(str).str.rstrip('%').astype("float") if 'revol_util' in loans: loans["revol_util"] = loans["revol_util"].astype(str).str.rstrip('%').astype("float") # 4. 特征衍生(使用训练时相同公式) if 'installment' in loans and 'annual_inc' in loans: loans["installment_feat"] = loans["installment"] / ((loans["annual_inc"] + 1) / 12) # 5. 有序特征映射(使用训练时的映射字典) mapping_dict = preprocessor_params['mapping_dict'] for col, mapping in mapping_dict.items(): if col in loans: # 处理未知值,默认为0 loans[col] = loans[col].map(mapping).fillna(0).astype(int) # 6. 缺失值处理(使用训练时保存的策略) # 分类变量 cat_cols = preprocessor_params['categorical_columns'] for col in cat_cols: if col in loans: loans[col] = loans[col].fillna("Unknown") # 数值变量(使用训练时保存的均值) num_means = preprocessor_params['numerical_means'] for col, mean_value in num_means.items(): if col in loans: loans[col] = loans[col].fillna(mean_value) # 7. One-hot编码(对齐训练时的列结构) n_columns = preprocessor_params['onehot_columns'] expected_dummy_columns = preprocessor_params['onehot_encoder_columns'] # 创建空DataFrame用于存储结果 dummy_df = pd.DataFrame(columns=expected_dummy_columns) # 为每个分类列生成dummy变量 for col in n_columns: if col in loans: # 为当前列生成dummies col_dummies = pd.get_dummies(loans[col], prefix=col) # 对齐训练时的列结构 for expected_col in expected_dummy_columns: if expected_col in col_dummies: dummy_df[expected_col] = col_dummies[expected_col] else: # 如果该列不存在,则创建全0列 dummy_df[expected_col] = 0 # 合并dummy变量 loans = pd.concat([loans, dummy_df], axis=1) # 删除原始分类列 loans.drop(columns=[col for col in n_columns if col in loans.columns], inplace=True, errors='ignore') # 8. 特征缩放(使用训练时的缩放器参数) sc = preprocessor_params['scaler'] scaled_cols = [col for col in preprocessor_params['scaled_columns'] if col in loans.columns] if scaled_cols: loans[scaled_cols] = sc.transform(loans[scaled_cols]) # 9. 对齐最终特征列(确保与训练数据相同) final_columns = preprocessor_params['final_columns'] # 添加缺失列(用0填充) for col in final_columns: if col not in loans.columns: loans[col] = 0 # 移除多余列并保持顺序 processed_data = loans[final_columns] print(loans.columns) return processed_data 标题区域 st.markdown(“”" 风控违约预测系统 基于机器学习的信贷风险评估与预测 """, unsafe_allow_html=True) 页面布局 col1, col2 = st.columns([1, 1.5]) 左侧区域 - 图片和简介 with col1: st.markdown(“”" 智能风控系统 利用先进机器学习技术预测信贷违约风险 “”", unsafe_allow_html=True) # 使用在线图片作为占位符 st.image("https://images.unsplash.com/photo-1553877522-43269d4ea984?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1200&q=80", caption="智能风控系统示意图", use_column_width=True) st.markdown(""" 📈 系统功能 客户违约风险预测 高精度机器学习模型 可视化风险评估 批量数据处理 """, unsafe_allow_html=True) 右侧区域 - 功能选择 with col2: st.markdown(“”" 请选择操作类型 您可以选择训练新模型或使用现有模型进行预测 “”", unsafe_allow_html=True) # 功能选择 option = st.radio("", ["🚀 训练新模型 - 使用新数据训练预测模型", "🔍 推理预测 - 使用模型预测违约风险"], index=0, label_visibility="hidden") # 模型训练部分 if "训练新模型" in option: st.markdown(""" 模型训练 上传训练数据并训练新的预测模型 """, unsafe_allow_html=True) # 上传训练数据 train_file = st.file_uploader("上传训练数据 (CSV格式)", type=["csv"]) if train_file is not None: try: # 读取数据 train_data_old = pd.read_csv(train_file) # 显示数据预览 with st.expander("数据预览", expanded=True): st.dataframe(train_data_old.head()) col1, col2, col3 = st.columns(3) col1.metric("总样本数", train_data_old.shape[0]) col2.metric("特征数量", train_data_old.shape[1] - 1) # 训练参数设置 st.subheader("训练参数") col1, col2 = st.columns(2) test_size = col1.slider("测试集比例", 0.1, 0.4, 0.2, 0.1) n_estimators = col2.slider("树的数量", 10, 500, 100, 10) max_depth = col1.slider("最大深度", 2, 30, 10, 1) random_state = col2.number_input("随机种子", 0, 100, 42) # 开始训练按钮 if st.button("开始训练模型", use_container_width=True): with st.spinner("模型训练中,请稍候..."): # 模拟数据处理 progress_bar = st.progress(0) train_data,preprocessor_params = preprocess_loan_data(train_data_old) joblib.dump(preprocessor_params, 'loan_preprocessor_params.pkl') # 步骤1: 数据预处理 time.sleep(1) progress_bar.progress(25) st.success("✅ 数据预处理完成") # 步骤2: 特征工程 time.sleep(1) progress_bar.progress(50) st.success("✅ 特征工程完成") # 步骤3: 模型训练 time.sleep(2) progress_bar.progress(75) # 实际训练代码 (简化版) X = train_data.drop("loan_status", axis=1) y = train_data["loan_status"] # 划分训练测试集 #todo 自己补齐数据划分代码 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size, random_state=random_state, stratify=y) # 训练模型 #todo 自己补齐调用随机森林算法完成模型的训练 model = RandomForestClassifier(n_estimators=n_estimators, max_depth=max_depth, random_state=random_state, n_jobs=-1) model.fit(X_train, y_train) # 保存模型 joblib.dump(model, "risk_model.pkl") # 步骤4: 模型评估 time.sleep(1) progress_bar.progress(100) # 评估模型 #todo 自己补齐调用预测函数完成测试集推理预测 y_pred = model.predict(X_test) y_proba = model.predict_proba(X_test)[:, 1] accuracy = accuracy_score(y_test, y_pred) auc = roc_auc_score(y_test, y_proba) # 保存评估结果 st.session_state.model_trained = True st.session_state.accuracy = accuracy st.session_state.auc = auc st.session_state.y_test = y_test st.session_state.y_pred = y_pred st.success("🎉 模型训练完成!") # 显示模型性能 st.subheader("模型性能评估") col1, col2 = st.columns(2) col1.markdown(f""" {accuracy*100:.1f}% 准确率 """, unsafe_allow_html=True) col2.markdown(f""" {auc:.3f} AUC 分数 """, unsafe_allow_html=True) # 混淆矩阵 st.subheader("混淆矩阵") cm = confusion_matrix(y_test, y_pred) fig, ax = plt.subplots(figsize=(6, 4)) sns.heatmap(cm, annot=True, fmt="d", cmap="Blues", ax=ax) ax.set_xlabel("预测标签") ax.set_ylabel("真实标签") ax.set_title("混淆矩阵") st.pyplot(fig) # 特征重要性 st.subheader("特征重要性") feature_importance = pd.DataFrame({ "特征": X.columns, "重要性": model.feature_importances_ }).sort_values("重要性", ascending=False).head(10) fig, ax = plt.subplots(figsize=(10, 6)) sns.barplot(x="重要性", y="特征", data=feature_importance, palette="viridis", ax=ax) ax.set_title("Top 10 重要特征") st.pyplot(fig) except Exception as e: st.error(f"数据处理错误: {str(e)}") # 推理预测部分 else: st.markdown(""" 风险预测 上传需要预测的数据,生成违约风险评估报告 """, unsafe_allow_html=True) # 上传预测数据 predict_file = st.file_uploader("上传预测数据 (CSV格式)", type=["csv"]) if predict_file is not None: try: # 读取数据 predict_data = pd.read_csv(predict_file) # 显示数据预览 with st.expander("数据预览", expanded=True): st.dataframe(predict_data.head()) st.info(f"数据集包含 {predict_data.shape[0]} 个样本,{predict_data.shape[1]} 个特征") # 检查是否有模型 if not os.path.exists("risk_model.pkl"): st.warning("⚠️ 未找到训练好的模型,请先训练模型或使用示例数据") # 使用示例模型 if st.button("使用示例模型进行预测", use_container_width=True): st.info("正在使用预训练的示例模型进行预测...") # 创建示例模型 X = np.random.rand(100, 10) y = np.random.randint(0, 2, 100) model = RandomForestClassifier(n_estimators=50, random_state=42) model.fit(X, y) # 生成预测结果 predictions = model.predict(predict_data.values) probas = model.predict_proba(predict_data.values)[:, 1] # 创建结果DataFrame result_df = pd.DataFrame({ "客户ID": predict_data["member_id"], "违约概率": probas, "预测标签": predictions }) # 添加风险等级 result_df["风险等级"] = pd.cut( result_df["违约概率"], bins=[0, 0.2, 0.5, 1], labels=["低风险", "中风险", "高风险"], include_lowest=True ) # 保存结果 st.session_state.prediction_results = result_df else: # 加载模型 model = joblib.load("risk_model.pkl") preprocessor_params = joblib.load('loan_preprocessor_params.pkl') # 开始预测按钮 if st.button("开始风险预测", use_container_width=True): with st.spinner("预测进行中,请稍候..."): # 模拟预测过程 progress_bar = st.progress(0) # 预处理推理数据 #todo 自己补齐调用推理数据处理函数完成推理数据的清洗 processed_inference = preprocess_loan_data_inference(predict_data, preprocessor_params) # 步骤1: 数据预处理 time.sleep(1) progress_bar.progress(25) # 步骤2: 特征工程 time.sleep(1) progress_bar.progress(50) # 步骤3: 模型预测 time.sleep(1) progress_bar.progress(75) # 生成预测结果 predictions = model.predict(processed_inference.values) probas = model.predict_proba(processed_inference.values)[:, 1] # 创建结果DataFrame result_df = pd.DataFrame({ "客户ID": predict_data["member_id"], "违约概率": probas, "预测标签": predictions }) # 添加风险等级 result_df["风险等级"] = pd.cut( result_df["违约概率"], bins=[0, 0.2, 0.5, 1], labels=["低风险", "中风险", "高风险"], include_lowest=True ) # 步骤4: 生成报告 time.sleep(1) progress_bar.progress(100) # 保存结果 st.session_state.prediction_results = result_df st.success("✅ 预测完成!") except Exception as e: st.error(f"预测错误: {str(e)}") # 显示预测结果 if "prediction_results" in st.session_state: st.markdown(""" 预测结果 客户违约风险评估报告 """, unsafe_allow_html=True) result_df = st.session_state.prediction_results # 风险分布 st.subheader("风险分布概览") col1, col2, col3 = st.columns(3) high_risk = (result_df["风险等级"] == "高风险").sum() med_risk = (result_df["风险等级"] == "中风险").sum() low_risk = (result_df["风险等级"] == "低风险").sum() col1.markdown(f""" {high_risk} 高风险客户 """, unsafe_allow_html=True) col2.markdown(f""" {med_risk} 中风险客户 """, unsafe_allow_html=True) col3.markdown(f""" {low_risk} 低风险客户 """, unsafe_allow_html=True) # 风险分布图 fig, ax = plt.subplots(figsize=(8, 4)) risk_counts = result_df["风险等级"].value_counts() risk_counts.plot(kind="bar", color=["#4CAF50", "#FFC107", "#F44336"], ax=ax) ax.set_title("客户风险等级分布") ax.set_xlabel("风险等级") ax.set_ylabel("客户数量") st.pyplot(fig) # 详细预测结果 st.subheader("详细预测结果") # 样式函数 def color_risk(val): if val == "高风险": return "background-color: #ffcdd2; color: #c62828;" elif val == "中风险": return "background-color: #fff9c4; color: #f57f17;" else: return "background-color: #c8e6c9; color: #388e3c;" # 格式化显示 styled_df = result_df.style.applymap(color_risk, subset=["风险等级"]) st.dataframe(styled_df.format({ "违约概率": "{:.2%}" }), height=400) # 下载结果 csv = result_df.to_csv(index=False).encode("utf-8") st.download_button( label="下载预测结果", data=csv, file_name="风险预测结果.csv", mime="text/csv", use_container_width=True ) 页脚 st.markdown(“—”) st.markdown(“”" © 2023 风控违约预测系统 | 基于Streamlit开发 """, unsafe_allow_html=True)

# -*- coding:utf-8 -*- import os import pandas as pd import asyncio import sys from datetime import datetime import uvloop from volcenginesdkarkruntime import AsyncArk # 读取CSV check_wfy = pd.read_csv('/data3/users/duant/2025-3-19/have.csv') output_path = '/data3/users/zhuominli/语言模型探索/推理任务/wfy' # 读取提示文件 with open('/data3/users/zhuominli/语言模型探索/推理任务/prompt.txt', 'r', encoding='utf-8') as f: prompt = f.read() async def worker(worker_id, task_num, conditions, results): client = AsyncArk() print(f"Worker {worker_id} is starting.") for i in range(task_num): if i >= len(conditions): break print(f"Worker {worker_id} task {i} is running.") try: completion = await client.batch_chat.completions.create( model="ep-bi-20250319172923-vzttd", messages=[ {"role": "system", "content": prompt}, {"role": "user", "content": str(conditions[i])}, ], temperature = 0.6 ) results[i] = completion.choices[0].message.content except Exception as e: print(f"Worker {worker_id} task {i} failed with error: {e}") else: print(f"Worker {worker_id} task {i} is completed.") # Display progress print(f"Worker {worker_id} progress: {i+1}/{task_num} tasks completed.") print(f"Worker {worker_id} is completed.") async def main(): start = datetime.now() # 核心参数配置 CONCURRENT_LIMIT = 1000 # 并发请求数 BATCH_SIZE = 5000 # 每批处理量 MAX_RETRIES = 3 # 最大重试次数 TIMEOUT = 30 # 单请求超时(秒) # 初始化共享客户端 client = AsyncArk() # 分批次处理 conditions = check_wfy['EXAM_FINDING'].tolist() total_batches = (len(conditions) + BATCH_SIZE - 1) // BATCH_SIZE for batch_idx in range(0, len(conditions), BATCH_SIZE): batch_cond = conditions[batch_idx:batch_idx+BATCH_SIZE] results = [None] * len(batch_cond) semaphore = asyncio.Semaphore(CONCURRENT_LIMIT) async def process_item(i): async with semaphore: for retry in range(MAX_RETRIES): try: completion = await client.batch_chat.completions.create( model="ep-bi-20250319172923-vzttd", messages=[ {"role": "system", "content": prompt}, {"role": "user", "content": str(batch_cond[i])}, ], temperature = 0.6 # Removed the request_timeout parameter that was causing the error ) results[i] = completion.choices[0].message.content return except Exception as e: if retry == MAX_RETRIES-1: results[i] = f"Failed after {MAX_RETRIES} retries: {str(e)}" await asyncio.sleep(2 ** retry) tasks = [process_item(i) for i in range(len(batch_cond))] await asyncio.gather(*tasks) # 实时保存批次结果 output_file = os.path.join(output_path, 'results.csv') pd.DataFrame({'condition': batch_cond, 'ds_v3': results})\ .to_csv(output_file, mode='a', header=not os.path.exists(output_file)) # Display batch progress print(f"Batch {batch_idx // BATCH_SIZE + 1}/{total_batches} completed.") print(f"Total time: {datetime.now() - start}") if __name__ == "__main__": if sys.version_info >= (3, 11): with asyncio.Runner(loop_factory=uvloop.new_event_loop) as runner: runner.run(main()) else: uvloop.install() asyncio.run(main()) # 20767

import streamlit as st import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from pyspark.sql import SparkSession from pyspark.ml.feature import VectorAssembler, StringIndexer, OneHotEncoder from pyspark.ml import Pipeline from pyspark.ml.classification import LogisticRegression, DecisionTreeClassifier, RandomForestClassifier from pyspark.ml.evaluation import BinaryClassificationEvaluator, MulticlassClassificationEvaluator import os import time import warnings import tempfile import shutil import traceback # 忽略警告 warnings.filterwarnings("ignore") # 设置中文字体 plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False # 页面设置 st.set_page_config( page_title="精准营销系统", page_icon="📊", layout="wide", initial_sidebar_state="expanded" ) # 自定义CSS样式 st.markdown(""" <style> .stApp { background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); font-family: 'Helvetica Neue', Arial, sans-serif; } .header { background: linear-gradient(90deg, #1a237e 0%, #283593 100%); color: white; padding: 1.5rem; border-radius: 0.75rem; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin-bottom: 2rem; } .card { background: white; border-radius: 0.75rem; padding: 1rem; margin-bottom: 1.5rem; box-shadow: 0 4px 12px rgba(0,0,0,0.1); transition: transform 0.3s ease; } .card:hover { transform: translateY(-5px); box-shadow: 0 6px 16px rgba(0,0,0,0.12); } .stButton button { background: linear-gradient(90deg, #3949ab 0%, #1a237e 100%) !important; color: white !important; border: none !important; border-radius: 0.5rem; padding: 0.75rem 1.5rem; font-size: 1rem; font-weight: 600; transition: all 0.3s ease; width: 100%; } .stButton button:hover { transform: scale(1.05); box-shadow: 0 4px 8px rgba(57, 73, 171, 0.4); } .feature-box { background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%); border-radius: 0.75rem; padding: 1.5rem; margin-bottom: 1.5rem; } .result-box { background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%); border-radius: 0.75rem; padding: 1.5rem; margin-top: 1.5rem; } .model-box { background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%); border-radius: 0.75rem; padding: 1.5rem; margin-top: 1.5rem; } .stProgress > div > div > div { background: linear-gradient(90deg, #2ecc71 0%, #27ae60 100%) !important; } .metric-card { background: white; border-radius: 0.75rem; padding: 1rem; text-align: center; box-shadow: 0 4px 8px rgba(0,0,0,0.06); } .metric-value { font-size: 1.8rem; font-weight: 700; color: #1a237e; } .metric-label { font-size: 0.9rem; color: #5c6bc0; margin-top: 0.5rem; } .highlight { background: linear-gradient(90deg, #ffeb3b 0%, #fbc02d 100%); padding: 0.2rem 0.5rem; border-radius: 0.25rem; font-weight: 600; } .stDataFrame { border-radius: 0.75rem; box-shadow: 0 4px 8px rgba(0,0,0,0.06); } .convert-high { background-color: #c8e6c9 !important; color: #388e3c !important; font-weight: 700; } .convert-low { background-color: #ffcdd2 !important; color: #c62828 !important; font-weight: 600; } .java-success { background-color: #d4edda; border-left: 4px solid #28a745; padding: 1rem; margin-bottom: 1.5rem; border-radius: 0 0.25rem 0.25rem 0; } </style> """, unsafe_allow_html=True) # 创建优化的Spark会话 def create_spark_session(): """创建优化的Spark会话,使用高效的配置参数""" try: # 基础配置 - 优化资源使用 builder = SparkSession.builder \ .appName("TelecomPrecisionMarketing") \ .config("spark.driver.memory", "1g") \ .config("spark.executor.memory", "1g") \ .config("spark.sql.shuffle.partitions", "4") \ .config("spark.network.timeout", "800s") \ .config("spark.executor.heartbeatInterval", "60s") \ .config("spark.sql.legacy.allowUntypedScalaUDF", "true") \ .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer") \ .config("spark.kryoserializer.buffer.max", "128m") \ .config("spark.driver.maxResultSize", "1g") \ .config("spark.sql.execution.arrow.pyspark.enabled", "true") \ .config("spark.ui.showConsoleProgress", "false") # 创建会话 spark = builder.getOrCreate() # 验证会话 try: # 创建测试DataFrame验证会话是否正常工作 test_df = spark.createDataFrame([(1, "test"), (2, "session")], ["id", "name"]) test_df_count = test_df.count() if test_df_count == 2: st.success("Spark会话创建成功并验证通过") else: st.warning(f"Spark会话验证异常,返回记录数: {test_df_count}") except Exception as e: st.error(f"Spark会话验证失败: {str(e)}") spark.stop() raise return spark except Exception as e: st.error(f"创建Spark会话失败: {str(e)}") st.error("请检查Java版本和Spark配置") st.stop() # 数据预处理函数 def preprocess_data(df): """优化后的数据预处理函数""" # 1. 选择关键特征 available_features = [col for col in df.columns if col in [ 'AGE', 'GENDER', 'ONLINE_DAY', 'TERM_CNT', 'IF_YHTS', 'MKT_STAR_GRADE_NAME', 'PROM_AMT_MONTH', 'is_rh_next' # 目标变量 ]] # 确保目标变量存在 if 'is_rh_next' not in available_features: st.error("错误:数据集中缺少目标变量 'is_rh_next'") return df # 只保留需要的列 df = df[available_features].copy() # 2. 处理缺失值 numeric_cols = ['AGE', 'ONLINE_DAY', 'TERM_CNT', 'PROM_AMT_MONTH'] for col in numeric_cols: if col in df.columns: median_val = df[col].median() df[col].fillna(median_val, inplace=True) categorical_cols = ['GENDER', 'MKT_STAR_GRADE_NAME', 'IF_YHTS'] for col in categorical_cols: if col in df.columns: mode_val = df[col].mode()[0] if not df[col].mode().empty else '未知' df[col].fillna(mode_val, inplace=True) # 3. 异常值处理 def handle_outliers(series): Q1 = series.quantile(0.25) Q3 = series.quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR return series.clip(lower_bound, upper_bound) for col in numeric_cols: if col in df.columns: df[col] = handle_outliers(df[col]) return df # 保存模型函数 def save_model(model, model_path): """保存模型到指定路径""" try: # 确保目录存在 os.makedirs(model_path, exist_ok=True) # 保存模型 model.write().overwrite().save(model_path) return True except Exception as e: st.error(f"保存模型失败: {str(e)}") st.error(traceback.format_exc()) return False # 标题区域 st.markdown(""" 精准营销系统 基于机器学习的单宽转融预测 """, unsafe_allow_html=True) # 页面布局 col1, col2 = st.columns([1, 1.5]) # 左侧区域 - 图片和简介 with col1: st.markdown(""" 📱 智能营销系统 预测单宽带用户转化为融合套餐用户的可能性 """, unsafe_allow_html=True) # 使用在线图片作为占位符 - 修复了use_column_width参数 st.image("https://images.unsplash.com/photo-1551836022-d5d88e9218df?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1200&q=80", caption="精准营销系统示意图", use_container_width=True) st.markdown(""" 📈 系统功能 用户转化可能性预测 高精度机器学习模型 可视化数据分析 精准营销策略制定 """, unsafe_allow_html=True) # 右侧区域 - 功能选择 with col2: st.markdown(""" 📋 请选择操作类型 您可以选择数据分析或使用模型进行预测 """, unsafe_allow_html=True) # 功能选择 option = st.radio("操作类型", ["📊 数据分析 - 探索数据并训练模型", "🔍 预测分析 - 预测用户转化可能性"], index=0) # 数据分析部分 if "数据分析" in option: st.markdown(""" 数据分析与模型训练 上传数据并训练预测模型 """, unsafe_allow_html=True) # 上传训练数据 train_file = st.file_uploader("上传数据集 (CSV格式, GBK编码)", type=["csv"]) if train_file is not None: try: # 读取数据 train_data = pd.read_csv(train_file, encoding='GBK') # 显示数据预览 with st.expander("数据预览", expanded=True): st.dataframe(train_data.head()) col1, col2 = st.columns(2) col1.metric("总样本数", train_data.shape[0]) col2.metric("特征数量", train_data.shape[1] - 1) # 数据预处理 st.subheader("数据预处理") with st.spinner("数据预处理中..."): processed_data = preprocess_data(train_data) st.success("✅ 数据预处理完成") # 可视化数据分布 st.subheader("数据分布分析") # 目标变量分布 st.markdown("**目标变量分布 (is_rh_next)**") fig, ax = plt.subplots(figsize=(8, 5)) sns.countplot(x='is_rh_next', data=processed_data, palette='viridis') plt.title('用户转化分布 (0:未转化, 1:转化)') plt.xlabel('是否转化') plt.ylabel('用户数量') st.pyplot(fig) # 数值特征分布 st.markdown("**数值特征分布**") numeric_cols = ['AGE', 'ONLINE_DAY', 'TERM_CNT', 'PROM_AMT_MONTH'] # 动态计算子图布局 num_features = len(numeric_cols) if num_features > 0: ncols = 2 nrows = (num_features + ncols - 1) // ncols fig, axes = plt.subplots(nrows, ncols, figsize=(14, 4*nrows)) if nrows > 1 or ncols > 1: axes = axes.flatten() else: axes = [axes] for i, col in enumerate(numeric_cols): if col in processed_data.columns and i < len(axes): sns.histplot(processed_data[col], kde=True, ax=axes[i], color='skyblue') axes[i].set_title(f'{col}分布') axes[i].set_xlabel('') for j in range(i+1, len(axes)): axes[j].set_visible(False) plt.tight_layout() st.pyplot(fig) else: st.warning("没有可用的数值特征") # 特征相关性分析 st.markdown("**特征相关性热力图**") corr_cols = numeric_cols + ['is_rh_next'] if len(corr_cols) > 1: corr_data = processed_data[corr_cols].corr() fig, ax = plt.subplots(figsize=(12, 8)) sns.heatmap(corr_data, annot=True, fmt=".2f", cmap='coolwarm', ax=ax) plt.title('特征相关性热力图') st.pyplot(fig) else: st.warning("特征不足,无法生成相关性热力图") # 模型训练 st.subheader("模型训练") # 训练参数设置 col1, col2 = st.columns(2) test_size = col1.slider("测试集比例", 0.1, 0.4, 0.2, 0.05) random_state = col2.number_input("随机种子", 0, 100, 42) # 开始训练按钮 if st.button("开始训练模型", use_container_width=True): # 创建临时目录用于存储模型 with tempfile.TemporaryDirectory() as tmp_dir: # 修复路径问题:使用绝对路径 model_path = os.path.abspath(os.path.join(tmp_dir, "best_model")) progress_bar = st.progress(0) status_text = st.empty() # 步骤1: 创建Spark会话 status_text.text("步骤1/7: 初始化Spark会话...") spark = create_spark_session() progress_bar.progress(15) # 步骤2: 转换为Spark DataFrame status_text.text("步骤2/7: 转换数据为Spark格式...") spark_df = spark.createDataFrame(processed_data) progress_bar.progress(30) # 步骤3: 划分训练集和测试集 status_text.text("步骤3/7: 划分训练集和测试集...") train_df, test_df = spark_df.randomSplit([1.0 - test_size, test_size], seed=random_state) progress_bar.progress(40) # 步骤4: 特征工程 status_text.text("步骤4/7: 特征工程处理...") categorical_cols = ['GENDER', 'MKT_STAR_GRADE_NAME', 'IF_YHTS'] existing_cat_cols = [col for col in categorical_cols if col in processed_data.columns] # 创建特征处理管道 indexers = [StringIndexer(inputCol=col, outputCol=col+"_index") for col in existing_cat_cols] encoders = [OneHotEncoder(inputCol=col+"_index", outputCol=col+"_encoded") for col in existing_cat_cols] numeric_cols = ['AGE', 'ONLINE_DAY', 'TERM_CNT', 'PROM_AMT_MONTH'] feature_cols = numeric_cols + [col+"_encoded" for col in existing_cat_cols] assembler = VectorAssembler(inputCols=feature_cols, outputCol="features") label_indexer = StringIndexer(inputCol="is_rh_next", outputCol="label") progress_bar.progress(50) # 步骤5: 构建模型 status_text.text("步骤5/7: 构建和训练模型...") # 使用优化的模型配置 rf = RandomForestClassifier( featuresCol="features", labelCol="label", numTrees=50, # 增加树的数量提高精度 maxDepth=5, # 适当增加深度 seed=random_state, featureSubsetStrategy="auto", # 自动选择特征子集策略 impurity="gini" # 使用基尼不纯度 ) pipeline = Pipeline(stages=indexers + encoders + [assembler, label_indexer, rf]) model = pipeline.fit(train_df) progress_bar.progress(80) # 步骤6: 评估模型 status_text.text("步骤6/7: 评估模型性能...") predictions = model.transform(test_df) evaluator_auc = BinaryClassificationEvaluator(labelCol="label", rawPredictionCol="rawPrediction") evaluator_acc = MulticlassClassificationEvaluator(labelCol="label", predictionCol="prediction", metricName="accuracy") evaluator_f1 = MulticlassClassificationEvaluator(labelCol="label", predictionCol="prediction", metricName="f1") auc = evaluator_auc.evaluate(predictions) acc = evaluator_acc.evaluate(predictions) f1 = evaluator_f1.evaluate(predictions) results = { "Random Forest": {"AUC": auc, "Accuracy": acc, "F1 Score": f1} } progress_bar.progress(95) # 步骤7: 保存结果 status_text.text("步骤7/7: 保存模型和结果...") # 保存模型 if save_model(model, model_path): st.session_state.model_results = results st.session_state.best_model = model st.session_state.model_path = model_path st.session_state.spark = spark progress_bar.progress(100) st.success("🎉 模型训练完成!") # 显示模型性能 st.subheader("模型性能评估") results_df = pd.DataFrame(results).T st.dataframe(results_df.style.format("{:.4f}").background_gradient(cmap='Blues')) # 特征重要性 st.subheader("特征重要性") rf_model = model.stages[-1] feature_importances = rf_model.featureImportances.toArray() importance_df = pd.DataFrame({ "Feature": feature_cols, "Importance": feature_importances }).sort_values("Importance", ascending=False).head(10) fig, ax = plt.subplots(figsize=(10, 6)) sns.barplot(x="Importance", y="Feature", data=importance_df, palette="viridis", ax=ax) plt.title('Top 10 重要特征') st.pyplot(fig) # 提供模型下载 st.subheader("模型持久化") st.info("模型已保存到临时目录,建议下载保存到本地") # 创建模型压缩包 with st.spinner("准备模型下载包..."): zip_path = shutil.make_archive( base_name=os.path.join(tmp_dir, "marketing_model"), format='zip', root_dir=model_path ) with open(zip_path, "rb") as f: st.download_button( label="下载完整模型", data=f, file_name="marketing_model.zip", mime="application/zip" ) else: st.error("❌ 模型保存失败,请查看错误日志") except Exception as e: st.error(f"模型训练错误: {str(e)}") st.error(traceback.format_exc()) st.error("提示:请检查数据格式和特征列名") # 预测分析部分 else: st.markdown(""" 用户转化预测 预测单宽带用户转化为融合套餐的可能性 """, unsafe_allow_html=True) # 上传预测数据 predict_file = st.file_uploader("上传预测数据 (CSV格式, GBK编码)", type=["csv"]) if predict_file is not None: try: # 读取数据 predict_data = pd.read_csv(predict_file, encoding='GBK') # 显示数据预览 with st.expander("数据预览", expanded=True): st.dataframe(predict_data.head()) # 检查是否有模型 if "best_model" not in st.session_state: st.warning("⚠️ 未找到训练好的模型,请先训练模型") st.stop() # 开始预测按钮 if st.button("开始预测", use_container_width=True): progress_bar = st.progress(0) status_text = st.empty() # 步骤1: 数据预处理 status_text.text("步骤1/4: 数据预处理中...") processed_data = preprocess_data(predict_data) progress_bar.progress(25) # 步骤2: 创建Spark会话 status_text.text("步骤2/4: 初始化Spark会话...") if "spark" not in st.session_state: spark = create_spark_session() st.session_state.spark = spark else: spark = st.session_state.spark progress_bar.progress(50) # 步骤3: 预测 status_text.text("步骤3/4: 进行预测...") spark_df = spark.createDataFrame(processed_data) best_model = st.session_state.best_model predictions = best_model.transform(spark_df) progress_bar.progress(75) # 步骤4: 处理结果 status_text.text("步骤4/4: 处理预测结果...") predictions_df = predictions.select( "CCUST_ROW_ID", "probability", "prediction" ).toPandas() # 解析概率值 predictions_df['转化概率'] = predictions_df['probability'].apply(lambda x: float(x[1])) predictions_df['预测结果'] = predictions_df['prediction'].apply(lambda x: "可能转化" if x == 1.0 else "可能不转化") # 添加转化可能性等级 predictions_df['转化可能性'] = pd.cut( predictions_df['转化概率'], bins=[0, 0.3, 0.7, 1], labels=["低可能性", "中可能性", "高可能性"] ) # 保存结果 st.session_state.prediction_results = predictions_df progress_bar.progress(100) st.success("✅ 预测完成!") except Exception as e: st.error(f"预测错误: {str(e)}") st.error(traceback.format_exc()) # 显示预测结果 if "prediction_results" in st.session_state: st.markdown(""" 预测结果 用户转化可能性评估报告 """, unsafe_allow_html=True) result_df = st.session_state.prediction_results # 转化可能性分布 st.subheader("转化可能性分布概览") col1, col2, col3 = st.columns(3) high_conv = (result_df["转化可能性"] == "高可能性").sum() med_conv = (result_df["转化可能性"] == "中可能性").sum() low_conv = (result_df["转化可能性"] == "低可能性").sum() col1.markdown(f""" {high_conv} 高可能性用户 """, unsafe_allow_html=True) col2.markdown(f""" {med_conv} 中可能性用户 """, unsafe_allow_html=True) col3.markdown(f""" {low_conv} 低可能性用户 """, unsafe_allow_html=True) # 转化可能性分布图 fig, ax = plt.subplots(figsize=(8, 5)) conv_counts = result_df["转化可能性"].value_counts() conv_counts.plot(kind='bar', color=['#4CAF50', '#FFC107', '#F44336'], ax=ax) plt.title('用户转化可能性分布') plt.xlabel('可能性等级') plt.ylabel('用户数量') st.pyplot(fig) # 详细预测结果 st.subheader("详细预测结果") # 样式函数 def color_convert(val): if val == "高可能性": return "background-color: #c8e6c9; color: #388e3c;" elif val == "中可能性": return "background-color: #fff9c4; color: #f57f17;" else: return "background-color: #ffcdd2; color: #c62828;" # 格式化显示 display_df = result_df[["CCUST_ROW_ID", "转化概率", "预测结果", "转化可能性"]] styled_df = display_df.style.format({ "转化概率": "{:.2%}" }).applymap(color_convert, subset=["转化可能性"]) st.dataframe(styled_df, height=400) # 下载结果 csv = display_df.to_csv(index=False).encode("utf-8") st.download_button( label="下载预测结果", data=csv, file_name="用户转化预测结果.csv", mime="text/csv", use_container_width=True ) # 页脚 st.markdown("---") st.markdown(""" © 2023 精准营销系统 | 基于Spark和Streamlit开发 | 优化版Spark连接 """, unsafe_allow_html=True) 使用dockerdesktop运行上述代码详细步骤

大家在看

recommend-type

lingo语法例子。。PPT

这个PPT是清华大学制作的 我觉得很好很全面 欢迎大家下载
recommend-type

宏碁Acer 4741G驱动合集 for winxp 官方版_宏碁个人笔记本XP驱动

宏碁Acer4741G驱动合集包含了显卡、网卡、声卡等驱动,都是xp版本,因为笔记本自带win7系统,有想要装xp系统的就需要这个驱动包。哈哈,买回来的Acer4741G,宏基的本本,性价比高,I5的CPU才4K多点,系统是win7家庭版,感觉还是XP好用,就要换回来了,想把驱动找全,欢迎下载体验
recommend-type

cpp-sdk-samples:适用于Windows和Linux的Affdex SDK的示例应用

cpp-sdk-samples:适用于Windows和Linux的Affdex SDK的示例应用
recommend-type

ray-optics:光学系统的几何光线追踪

射线光学 安装 要使用pip安装rayoptics ,请使用 > pip install rayoptics 或者,可以使用conda从conda - forge渠道安装rayoptics > conda install rayoptics --channel conda-forge 文献资料 射线光学位于“ 成像光学设计和分析工具 RayOptics是一个Python几何光学和成像光学库。 它为分析成像和相干光学系统提供了几何射线追踪基础。 在此基础上提供了许多标准的几何分析选项,例如横向射线和波前像差分析。 y-ybar图和镜头布局视图中近轴光线的图形编辑也支持光学系统的近轴布局。 支持导入Zemax .zmx和CODEV .seq文件。 RayOptics可用于Python脚本,Python和IPython外壳,Jupyter笔记本以及基于Qt的图形用户界面应用程序中。 笔记 该项
recommend-type

java读取kml文件数据

自己做的用java读取kml文件数据,并保存为json文件。方便其它地方解析。

最新推荐

recommend-type

tika-parser-font-module-3.1.0.jar中文-英文对照文档.zip

1、压缩文件中包含: 中文-英文对照文档、jar包下载地址、Maven依赖、Gradle依赖、源代码下载地址。 2、使用方法: 解压最外层zip,再解压其中的zip包,双击 【index.html】 文件,即可用浏览器打开、进行查看。 3、特殊说明: (1)本文档为人性化翻译,精心制作,请放心使用; (2)只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; (3)不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 4、温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件。 5、本文件关键字: jar中文-英文对照文档.zip,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,开发手册,使用手册,参考手册。
recommend-type

HTML时间格式化工具及测试页面介绍

标题 "BoolStudio.github.io" 暗示这是一个与GitHub相关的在线资源,具体来说是与BoolStudio相关的网页地址。GitHub是一个著名的代码托管平台,它支持Git版本控制系统,允许用户在云端存储和共享代码。BoolStudio可能是GitHub上的一个用户或组织账户名称,而该页面可能是他们托管的项目或个人页面的入口。 描述中的信息包含了HTML元素和JavaScript代码片段。这段描述展示了一个测试页文件的部分代码,涉及到HTML的标题(title)和内嵌框架(iframe)的使用,以及JavaScript中Date对象的扩展功能。 从描述中我们可以分析出以下知识点: 1. HTML标题(Title): 在HTML中,`<title>`标签用于定义网页的标题,它会显示在浏览器的标题栏或页面的标签上。在描述中出现了`<title>现在时间</title>`,这表明网页的标题被设置为了“现在时间”。 2. 微软时间: 这可能指的是在网页中嵌入微软产品的日期和时间显示。尽管这部分内容在描述中被删除了,但微软时间通常与Windows操作系统的日期和时间显示相关联。 3. iframe元素: `<iframe>`标签定义了一个内嵌框架,可以在网页中嵌入另一个文档。在描述中出现的是`<iframe src"></iframe>`,这表示创建了一个空的iframe元素,其src属性为空,实际上没有嵌入任何内容。通常src属性会被设置为另一个HTML文档的URL,用来在当前页面中显示外部页面的内容。 4. JavaScript日期格式化: 描述中包含了一段JavaScript代码,这段代码扩展了Date对象的功能,允许它根据提供的格式字符串(fmt)返回格式化的日期和时间。例如,如果fmt是'y年M月d日 h时m分s秒',则该函数会按照这个格式返回当前日期和时间。 具体到代码实现,以下步骤展示了如何在JavaScript中扩展Date对象并格式化日期: - 首先创建了一个对象o,该对象包含日期和时间的不同部分,例如年(y)、月(M)、日(d)、时(h)、分(m)、秒(s)。 - 使用正则表达式检查格式字符串fmt中是否包含年份的占位符(y+),如果存在则将其替换为四位数的年份,不足部分用0补齐。 - 使用for...in循环遍历对象o中的每一个键值对,并使用正则表达式测试这些键(如年、月、日等)是否在格式字符串中存在。如果存在,则替换为相应的日期时间值。 这个知识点展示了在JavaScript中如何自定义对象的方法,以及如何使用正则表达式进行字符串的搜索和替换。 标签 "HTML" 表明这个文件主要涉及到超文本标记语言(HTML),这是构建网页内容的标准标记语言,它允许开发者使用标签来定义网页的结构和内容。HTML文件通常包含各种元素,如段落、链接、图片、表格和表单等。 压缩包子文件的文件名称列表中的 "BoolStudio.github.io-main" 可能指的是一个包含 "BoolStudio.github.io" 网站主要文件的压缩包。通常,网站项目会包含多个文件和资源,为了便于管理和分发,开发者会将这些文件打包成一个压缩文件。在本例中,"main" 可能表示该压缩包包含了网站的主要文件和代码。 通过对给定文件信息的分析,我们可以了解到网页设计和开发的相关知识点,特别是HTML文档结构、JavaScript对象和函数扩展以及如何在网站项目中组织文件。这些知识对于前端开发者来说是非常重要的,能够帮助他们创建更加动态和功能丰富的网页应用。
recommend-type

Elixir测试:从用例执行到覆盖率分析

### Elixir 测试:从用例执行到覆盖率分析 #### 1. 测试用例执行 在测试中,以 `Test` 结尾且包含 `use ExUnit` 的模块被视为测试用例。测试用例本质上是设置回调和测试的集合。测试用例的执行方式有两种:并发执行或顺序执行。使用 `async: true` 选项的测试用例将并发执行,其余的则按定义顺序(默认随机)顺序执行。 测试用例的执行流程如下: 1. 执行所有 `setup_all` 回调。 2. 执行所有测试及其特定的设置和拆卸回调。 `setup_all` 回调按定义顺序在同一进程中执行。在运行任何测试之前,测试用例将生成一个进程,在该进程中运行所有
recommend-type

Android Studio 时间延时

<think> 我们参考多个引用,总结在Android Studio中实现时间延时的方法。根据引用[1]和[2],我们知道有几种常见的延时方法,包括使用Handler的postDelayed方法、使用Timer和TimerTask以及使用Thread.sleep。引用[3]和[5]则展示了具体代码示例。 具体方法如下: 1. **使用Handler的postDelayed方法**(推荐在主线程中使用,避免直接操作UI线程的问题): ```java new Handler().postDelayed(new Runnable() { @Override
recommend-type

IMS Open Corpus Workbench:打造高效大型文本语料库管理工具

IMS Open Corpus Workbench(以下简称CWB)是一个强大的开源工具集,它专门用于管理和查询大型的、带有语言注释的文本语料库。这项工具有着广泛的应用领域,包括语言学研究、自然语言处理、人文科学研究等。 ### 标题知识点: #### 大型文本语料库的索引和查询工具 大型文本语料库指的是含有大量文本数据的数据库,其中包含的文本量通常以百万计。这些数据可能是书面文本、口语录音文字转写等形式。对于如此庞大的数据集,索引是必要的,它可以帮助研究者快速定位到感兴趣的片段,而查询工具则提供了从这些大量数据中提取特定信息的能力。 #### 开源 CWB作为一个开源工具,意味着其源代码对所有人开放,并且可以免费使用和修改。开源项目通常是由社区驱动,有着活跃的开发者和用户群体,不断对工具进行改进和拓展。这种模式促进了创新,并且有利于长期维护和升级。 ### 描述知识点: #### 管理和查询带有语言注释的文本 在语料库中,文本数据经常会被加上各种形式的语言注释,比如句法结构、词性标注、语义角色等。CWB支持管理这类富含语言信息的语料库,使其不仅仅保存原始文本信息,还整合了深层的语言知识。此外,CWB提供了多种查询语言注释数据的方式,使得用户可以针对特定的注释信息进行精确查询。 #### 核心组件:CQP(Corpus Query Processor) CQP是CWB中的核心组件,是一个高度灵活和高效的查询处理器。它支持在终端会话中交互式地使用,这为熟悉命令行界面的用户提供了一个强大的工具。同时,CQP也可以嵌入到其他程序中,比如Perl脚本,从而提供编程式的语料库访问方式。这为高级用户提供了一个强大的平台,可以编写复杂的查询,并将查询结果集成到其他程序中。 #### 基于Web的GUI CQPweb 除了命令行界面外,CWB还提供了一个基于Web的图形用户界面CQPweb,使得不熟悉命令行的用户也能够方便地使用CWB的强大功能。CQPweb通常允许用户通过网页直接构建查询,并展示查询结果,极大地降低了使用门槛。 ### 标签知识点: #### 开源软件 CWB作为开源软件,其主要特点和优势包括: - **社区支持**:开放源代码鼓励了全球开发者共同参与,提供错误修正、功能增强、新特性开发等。 - **定制化**:用户可以根据自己的需求对源代码进行修改,从而实现定制化的功能。 - **透明性**:源代码的开放确保了软件工作的透明性,用户可以清楚了解软件的工作原理和数据处理方式。 - **可靠性**:由于代码的公开性,很多用户和开发者可以共同审查代码,提高了软件的可靠性和安全性。 - **成本效益**:开源软件通常不需要支付昂贵的许可费用,对预算有限的个人和机构特别友好。 ### 压缩包子文件的文件名称列表知识点: #### cwb-3.0.0-osx-10.5-universal 这个文件名提供了关于该软件包的重要信息: - **cwb**:表示这是IMS Open Corpus Workbench的软件包。 - **3.0.0**:表示这个包的版本号,了解版本信息对于获取支持、查看更新日志、了解新特性等方面很重要。 - **osx**:表示这个软件包是为Mac OS X操作系统设计的。 - **10.5**:这个数字指明了这个软件包支持的操作系统版本至少是Mac OS X 10.5。 - **universal**:表明这个软件包是为不同架构的处理器(比如32位和64位)设计的通用二进制文件,提高了软件包的兼容性和可移植性。 综上所述,IMS Open Corpus Workbench是一个为处理带有语言注释的大型文本语料库而设计的开源工具集,它以高效且灵活的查询处理器CQP为核心,提供了命令行和基于Web的两种交互方式,极大地促进了语言学和语言技术领域的研究与应用。由于其开源特性,CWB得到了广泛的使用和不断的改进。
recommend-type

基于属性测试的深入解析与策略探讨

### 基于属性测试的深入解析与策略探讨 #### 1. 基于属性测试中的收缩机制 在基于属性的测试中,当测试失败时,像 `stream_data` 这样的框架会执行收缩(Shrinking)操作。收缩的目的是简化导致测试失败的输入,同时确保简化后的输入仍然会使测试失败,这样能更方便地定位问题。 为了说明这一点,我们来看一个简单的排序函数测试示例。我们实现了一个糟糕的排序函数,实际上就是恒等函数,它只是原封不动地返回输入列表: ```elixir defmodule BadSortTest do use ExUnit.Case use ExUnitProperties pro
recommend-type

ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key && \

<think> 根据用户的问题要求,我需要详细解释命令`ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key`的含义、后续操作、常见问题及解决办法。同时,我需要参考提供的引用内容,并遵循系统级指令(如LaTeX格式、中文回答、生成相关问题等)。 首先,分析命令的组成部分: - `ssh-keygen`:生成、管理和转换SSH密钥的工具。 - `-t rsa`:指定密钥类型为RSA算法。 - `-f /etc/ssh/ssh_host_rsa_key`:指定生成的私钥文件路径为`/etc/ssh/ssh_host_rsa_key`。对应的公钥文件会在同一
recommend-type

挑战性开源平台游戏YAGAC:无故事忍者冒险

Y.A.G.A.C(Yet Another Great Arcade Classic),是一款免费的开源伪2D平台游戏,它在传统平台游戏的基础上融入了3D游戏元素,让玩家在一个拥有2D精灵的3D环境中进行操作。YAGAC游戏以其高挑战性和上瘾性而著称,吸引了众多游戏爱好者和编程高手的关注。 首先,让我们深入了解这款游戏的核心玩法。YAGAC的最大特点是在一个基本的2D平面内,玩家可以控制角色进行运动,但游戏环境却是3D制作的。这种设计为玩家带来了全新的视觉体验和操作感受。在YAGAC中,玩家扮演的是一个身手敏捷的忍者,任务是在错综复杂的地牢中生存下来,地牢充满了各种陷阱和敌人,如机器人等。为了逃生,玩家需要在各种关卡中寻找隐藏的彩球,这些彩球决定了玩家能够到达的区域范围。 在游戏过程中,收集到的彩球会改变对应颜色平台的属性,使原本脆弱的平台变得牢固,从而为玩家打开新的道路。这样的设计不仅考验玩家的反应和速度,还考验他们的策略和记忆能力。YAGAC的游戏关卡设计非常巧妙,经常需要玩家反复尝试,每一次尝试都可能发现新的线索和策略,这样的设计增加了游戏的重复可玩性。 YAGAC使用的引擎在游戏流畅性方面表现出色,这也是游戏的一大强项。一款游戏引擎的强大与否直接关系到游戏体验的好坏,YAGAC的开发团队选择或者开发了一个能够高效处理3D图形和2D动作的引擎,确保了游戏在各种配置的计算机上都能保持良好的运行状态和响应速度。 接下来,我们来探讨YAGAC的开源属性。由于YAGAC是开源的,这意味着游戏的源代码是开放的,任何个人或组织都可以访问、修改并重新分发该软件。开源软件通常由社区维护,并且鼓励用户贡献代码,共同改进游戏。对于像YAGAC这样的游戏来说,开源可以吸引更多的开发者参与进来,共同完善游戏体验。玩家和开发者可以对游戏进行本地化、修改游戏机制,甚至是增加新的内容和关卡。 开源平台游戏的概念不仅限于提供免费的游戏体验,它还鼓励创新和协作。开源文化促进知识共享和技术进步,这在软件开发领域尤为重要。同时,玩家也可以从中学到编程知识,因为游戏的开源性质允许用户查看和学习游戏背后的代码。 最后,关于YAGAC的文件名称“yagac”,这很可能是游戏的主程序文件名或者主目录名。当玩家在计算机上进行下载和安装时,可能会遇到这样的文件名,识别这一点有助于玩家快速定位和管理游戏文件。 总的来说,Y.A.G.A.C.作为一款开源平台游戏,在游戏玩法设计、开源文化和3D游戏引擎的流畅性等方面,都显示出其独特的优势。游戏的挑战性和上瘾性,以及社区驱动的开发模式,都为游戏爱好者和开源爱好者提供了一个理想的游戏和学习平台。对于对游戏开发感兴趣的玩家来说,YAGAC无疑是一个研究和实践的良好起点。
recommend-type

状态化属性测试与测试数据随机化及测试生命周期解析

# 状态化属性测试与测试数据随机化及测试生命周期解析 ## 状态化属性测试 ### 状态化系统测试的引入 之前我们主要探讨了针对纯无状态函数的属性测试,这类函数接收输入并返回输出。然而,属性测试同样适用于有状态系统的测试。有状态系统是指那些会携带状态的系统,例如数据库。 在之前的示例中,我们仅使用属性测试来生成数据,然后将其输入到代码中并对结果进行断言。但对于有状态系统,情况有所不同:我们需要处理状态的设置,并仅在系统处于特定状态时执行某些操作。 ### 对有状态系统进行建模 我们可以利用属性测试框架生成随机数据的能力,为有状态系统生成随机命令。例如,若有状态系统是数据库,我们就能生成针
recommend-type

icpc英语字典

<think> 首先,用户的问题是:“我想查找icpc相关的英语字典 请问icpc英语专业字典”。用户想查找与ICPC(International Collegiate Programming Contest)相关的英语字典,特别是专业字典。 现在,回顾系统指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式使用$$...$$。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识。 - 回答结构清晰,帮助用户逐步解决问题。 - 保持真实可靠。 用户提供了几个引用: - 引用[1]:关于填字游戏和寻找变形词的程序。