dplyr group_modify函数深度实测:3个真实案例告诉你它有多强大

第一章:dplyr group_modify函数深度实测:3个真实案例告诉你它有多强大

group_modify() 是 dplyr 中一个功能强大的分组操作函数,它允许用户在每个分组上应用自定义函数,并返回一个数据框作为结果。与 summarize() 不同,group_modify() 保留了原始分组结构,同时支持更复杂的变换逻辑。

按类别拟合线性模型并提取系数

在分组数据中拟合模型是常见需求。group_modify() 可以对每个分组执行建模操作并整合结果:


library(dplyr)

# 示例数据
data <- tibble(
  group = rep(c("A", "B"), each = 50),
  x = rnorm(100),
  y = rnorm(100) + x
)

result <- data %>%
  group_by(group) %>%
  group_modify(~ {
    model <- lm(y ~ x, data = .x)
    tibble(term = names(coef(model)), estimate = coef(model))
  })

# 输出每组的回归系数
print(result)

上述代码对每组独立拟合线性回归,并将系数整理为统一的数据框输出。

时间序列分组滚动计算

在金融或传感器数据分析中,常需对每个设备或账户进行滚动均值计算:

  • 使用 group_by(device_id) 按设备分组
  • 通过 group_modify() 应用滚动平均函数
  • 返回扩展后的结果数据框,保持原始行数结构

动态过滤异常子组

当需要根据组内统计量决定是否保留该组时,可结合条件判断:


clean_groups <- data %>%
  group_by(category) %>%
  group_modify(~ {
    if (nrow(.x) < 5 || sd(.x$value) > 10) {
      # 过滤掉样本过少或波动过大的组
      tibble()
    } else {
      .x  # 返回原始数据
    }
  })
函数适用场景输出结构要求
group_modify()复杂分组变换必须返回数据框
summarize()聚合统计单行汇总值

第二章:group_modify函数核心机制解析

2.1 理解group_modify的基本语法与设计哲学

`group_modify` 是 dplyr 中用于按组执行复杂数据转换的核心函数,其设计遵循“分组-应用-合并”的哲学。它接收一个分组后的数据框和一个函数,对每组独立应用该函数,并确保结果能被正确拼接。
基本语法结构

group_modify(data, function(.x, .y) { ... })
其中 `.x` 表示当前分组的数据(不包含分组变量),`.y` 为分组标识。函数必须返回一个数据框,以保证结构一致性。
设计哲学解析
  • 函数式编程思想:将操作封装为可复用的函数,提升代码表达力;
  • 类型安全:强制要求输出为数据框,避免运行时结构错误;
  • 并行友好:各组独立处理,天然支持未来扩展的并行执行。
该模式鼓励用户以“数据流”视角构建变换逻辑,增强可读性与模块化程度。

2.2 与group_map、summarize等分组操作的对比分析

在数据分组处理中,`group_map`、`summarize` 和 `group_modify` 各有侧重。`group_map` 适用于对每组应用函数并返回列表结果,灵活性高但性能开销较大;`summarize` 则聚焦于聚合统计,返回单行摘要值,适合快速汇总。
性能与返回结构对比
操作返回结构典型用途
group_map列表或数据框列表复杂组内变换
summarize单行聚合值统计指标计算
group_modify单一数据框标准化组运算
代码示例与说明

result <- data %>%
  group_by(category) %>%
  group_modify(~ lm(y ~ x, data = .x) %>% coef())
该代码对每组拟合线性模型并提取系数,`group_modify` 确保输出为统一数据框结构,相较 `group_map` 更易后续处理,体现其在结构一致性上的优势。

2.3 数据框列表处理模式下的性能表现

在大规模数据处理中,数据框列表(DataFrame List)的遍历与聚合操作常成为性能瓶颈。采用向量化操作替代显式循环可显著提升执行效率。
优化前的低效实现

# 逐个数据框进行循环处理
result = []
for df in df_list:
    aggregated = df.groupby('category')['value'].sum()
    result.append(aggregated)
该方式未利用底层并行机制,时间复杂度为 O(n×m),n 为数据框数量,m 为单个数据框行数。
向量化聚合策略
  • 使用 pd.concat 统一数据结构
  • 通过多级索引实现一键分组
  • 调用 .groupby().sum() 触发内部优化路径

combined = pd.concat(df_list, keys=range(len(df_list)))
result = combined.groupby('category')['value'].sum()
合并后操作减少函数调用开销,充分利用 Pandas 的 C++ 底层加速,性能提升可达 3-5 倍。

2.4 如何正确返回数据结构以避免错误

在构建API或服务接口时,统一且清晰的数据返回结构能显著降低客户端处理成本并减少运行时错误。
标准化响应格式
建议采用一致的封装结构返回数据,包含状态码、消息和数据体:
{
  "code": 200,
  "message": "请求成功",
  "data": {
    "userId": 123,
    "username": "alice"
  }
}
其中,code 表示业务状态码,message 提供可读提示,data 仅在成功时存在,避免返回 null 或未定义结构。
常见错误类型与规避
  • 不要直接返回原始数据,缺失上下文易引发解析异常
  • 禁止在成功响应中嵌入错误信息字段
  • 数组类型应始终返回空数组而非 null

2.5 非标准求值(NSE)在实际应用中的注意事项

在使用非标准求值(NSE)时,需特别注意其对代码可读性与调试带来的影响。NSE常用于dplyr等R语言包中,允许用户传入未加引号的变量名,但这也可能导致作用域混乱。
避免意外的变量捕获
NSE依赖于调用环境解析变量,若不明确指定上下文,可能捕获到错误的变量值。建议在函数内部使用enquo()!!进行显式捕捉与解引。

library(rlang)
my_summarize <- function(data, var) {
  var_enq <- enquo(var)
  summarise(data, mean = mean(!!var_enq, na.rm = TRUE))
}
上述代码通过enquo()捕获表达式,并利用!!在合适的作用域内展开,确保变量正确解析。
性能与调试权衡
  • NSE提升交互效率,但降低函数透明度;
  • 在模块化系统中建议封装为SE接口;
  • 调试时可借助expr_text()查看实际解析表达式。

第三章:案例驱动的进阶用法实践

3.1 案例一:分组拟合回归模型并提取系数

在数据分析中,常需按分组变量分别拟合回归模型。以 `iris` 数据集为例,按物种(Species)分组拟合花瓣长度对宽度的线性回归,并提取每组的斜率与截距。
实现流程
  • 使用 dplyr 进行分组操作
  • 结合 nest()map() 对每组数据拟合模型
  • 利用 broom::tidy() 提取模型系数

library(dplyr)
library(purrr)
library(broom)

iris %>%
  group_by(Species) %>%
  nest() %>%
  mutate(model = map(data, ~ lm(Petal.Length ~ Petal.Width, data = .)),
         coef = map(model, tidy)) %>%
  unnest(coef)
上述代码首先将数据按物种分组并嵌套,随后对每组数据应用线性回归。最终通过 tidy() 标准化输出,得到包含每组回归系数(估计值、标准误、p 值等)的整洁数据框,便于后续比较与可视化。

3.2 案例二:按组生成动态预测序列

在时间序列预测中,常需按分组字段(如用户ID、设备编号)独立生成预测序列。使用Pandas的`groupby`结合自定义函数可高效实现此需求。
核心实现逻辑
def generate_forecast(group):
    # 基于每组历史数据拟合简单线性趋势
    n = len(group)
    group['trend'] = group['value'].iloc[-1] + np.arange(1, 6) * group['value'].diff().mean()
    return pd.DataFrame({'forecast': group['trend']})

result = data.groupby('group_id').apply(generate_forecast).reset_index()
上述代码对每组数据计算历史增量均值,并基于此生成未来5步的线性预测。`groupby().apply()`确保各组独立处理,避免数据泄露。
输出结构示例
group_idstepforecast
A1102.3
A2104.6
B188.1

3.3 案例三:嵌套数据结构中的复杂聚合运算

在处理多层嵌套的数据结构时,聚合运算常面临路径解析与层级遍历的挑战。以JSON格式的销售数据为例,需统计每个区域下各产品的总销售额。
数据结构示例
{
  "region": "华东",
  "stores": [
    {
      "name": "店A",
      "sales": [
        {"product": "P1", "amount": 120},
        {"product": "P2", "amount": 80}
      ]
    }
  ]
}
该结构包含区域、门店和销售记录三层嵌套,需递归提取 sales 数组中的 amount 字段。
聚合实现逻辑
  • 使用深度优先遍历进入嵌套数组
  • 通过 reduce 累加相同 product 的 amount 值
  • 利用 Map 结构暂存中间结果以提升查找效率
性能优化建议
方法时间复杂度适用场景
递归 + 循环O(n*m)数据量小,结构固定
流式处理O(n)大数据集,内存受限

第四章:工程化场景下的最佳实践

4.1 结合purrr进行函数式编程提升可读性

在R语言中,`purrr`包为函数式编程提供了强大支持,显著提升代码的可读性与简洁性。通过高阶函数抽象循环逻辑,使数据处理流程更清晰。
核心函数简介
  • map():对列表或向量逐元素应用函数,返回列表;
  • map_dbl()map_chr():返回特定类型的向量;
  • reduce():将二元函数逐步应用于元素,实现累积操作。
代码示例

library(purrr)

# 将多个数值向量取平方后求均值
results <- list(c(1, 2), c(3, 4), c(5, 6)) %>%
  map(~ .x^2) %>%
  map_dbl(mean)
上述代码中,map(~ .x^2) 对每个子向量进行平方运算,map_dbl(mean) 计算每组均值并返回数值向量,链式操作逻辑清晰,避免显式循环。

4.2 在大规模数据中优化内存使用的策略

在处理大规模数据时,内存使用效率直接影响系统性能与稳定性。合理选择数据结构是优化的第一步。
使用高效的数据结构
优先采用内存紧凑的结构,如使用 struct 替代 class,或利用位字段压缩布尔标志。例如在 Go 中:

type User struct {
    ID      uint32
    Active  bool
    Deleted bool
}
该结构可进一步优化为:

type User struct {
    ID   uint32
    Flags byte // 使用位操作存储 Active 和 Deleted
}
通过位运算管理状态,显著减少内存占用。
对象池复用机制
频繁创建和销毁对象会加剧 GC 压力。使用对象池(sync.Pool)可有效复用内存实例:
  • 减少堆分配次数
  • 降低垃圾回收频率
  • 提升高并发场景下的响应速度

4.3 错误处理与调试技巧:定位分组中的异常组

在复杂的数据分组操作中,异常组往往导致聚合结果偏差。通过合理的错误捕获机制可快速定位问题源头。
使用结构化日志标记异常组
func processGroup(group map[string]interface{}) error {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("PANIC in group: %v, data: %v", r, group)
        }
    }()
    // 处理逻辑
    return nil
}
该函数通过 deferrecover 捕获运行时异常,并记录原始数据上下文,便于回溯。
常见异常类型对照表
异常类型可能原因建议措施
nil指针解引用未初始化的成员字段增加前置校验
类型断言失败数据类型不一致启用Schema验证

4.4 与dbplyr协同实现数据库端分组计算

数据库端分组的优势
使用 dbplyr 可在数据库内部执行分组聚合操作,避免数据全量拉取至本地。这不仅提升性能,还减少内存占用,特别适用于大规模数据集。
基本语法结构

library(dplyr)
con %>% 
  tbl("sales") %>% 
  group_by(region, product) %>% 
  summarise(total = sum(amount), .groups = 'drop')
该代码在数据库中按区域和产品分组,计算每组销售额总和。.groups = 'drop' 明确指定分组信息清理方式,避免警告。
执行计划解析
dbplyr 将上述管道转换为 SQL:

SELECT region, product, SUM(amount) AS total
FROM sales
GROUP BY region, product
所有计算在数据库端完成,R 仅接收最终结果,实现高效的数据处理闭环。

第五章:总结与展望

技术演进的持续驱动
现代软件架构正加速向云原生和边缘计算融合,Kubernetes 已成为容器编排的事实标准。企业级部署中,服务网格如 Istio 通过透明地注入流量控制能力,显著提升了微服务可观测性。
  • 自动化运维工具链(如 ArgoCD)实现 GitOps 持续交付
  • 可观测性体系需整合日志(Loki)、指标(Prometheus)与追踪(Jaeger)
  • 安全左移要求 CI/CD 流程集成 SAST 工具(如 SonarQube)
代码实践中的优化路径
在高并发场景下,Go 语言的轻量级协程展现出显著优势。以下为基于 context 控制的并发请求示例:

func fetchData(ctx context.Context, urls []string) error {
    var wg sync.WaitGroup
    errCh := make(chan error, len(urls))

    for _, url := range urls {
        wg.Add(1)
        go func(u string) {
            defer wg.Done()
            req, _ := http.NewRequestWithContext(ctx, "GET", u, nil)
            resp, err := http.DefaultClient.Do(req)
            if err != nil {
                select {
                case errCh <- err:
                default:
                }
                return
            }
            defer resp.Body.Close()
        }(url)
    }

    go func() {
        wg.Wait()
        close(errCh)
    }()

    select {
    case err := <-errCh:
        return err
    case <-ctx.Done():
        return ctx.Err()
    }
}
未来架构趋势预测
趋势方向关键技术典型应用场景
Serverless 边缘化Cloudflare Workers低延迟 API 网关
AIOps 深度集成Prometheus + ML 分析异常检测与根因定位
图表:下一代 DevOps 平台能力矩阵 —— 集成 AI 驱动的自动调参、跨云资源调度与零信任安全策略嵌入。
import pandas as pd import numpy as np import matplotlib.pyplot as plt from matplotlib import cm import matplotlib.font_manager as fm from scipy.interpolate import griddata, Rbf from scipy.ndimage import gaussian_filter import trimesh import os from datetime import datetime, timedelta import warnings warnings.filterwarnings(&#39;ignore&#39;) class SlopeTemperatureAnalysis: def __init__(self, temp_data_file, coord_data_file, stl_file, xy_slice_z=None, xz_slice_y=None, yz_slice_x=None): """ 初始化边坡温度分析系统 参数: temp_data_file: 测点-时间-温度变化表Excel文件路径 coord_data_file: 测点坐标表Excel文件路径 stl_file: 边坡三维STL文件路径 xy_slice_z: XY平面切片的Z坐标(mm),如果为None则使用平均高度 xz_slice_y: XZ平面切片的Y坐标(mm),如果为None则使用中心位置 yz_slice_x: YZ平面切片的X坐标(mm),如果为None则使用中心位置 """ # 设置中文字体 self.set_chinese_font() # 设置工作目录 self.base_dir = r&#39;D:\桌面\温度数据\三维温度图\工况1&#39; # 创建输出文件夹 self.output_dir = os.path.join(self.base_dir, &#39;分析结果&#39;) os.makedirs(self.output_dir, exist_ok=True) # 切片位置设置 self.xy_slice_z = xy_slice_z self.xz_slice_y = xz_slice_y self.yz_slice_x = yz_slice_x # 构建完整文件路径 temp_data_path = os.path.join(self.base_dir, temp_data_file) coord_data_path = os.path.join(self.base_dir, coord_data_file) stl_path = os.path.join(self.base_dir, stl_file) # 检查文件是否存在 self.check_files_exist(temp_data_path, coord_data_path, stl_path) # 加载数据 self.load_data(temp_data_path, coord_data_path) # 加载边坡模型 self.load_slope_model(stl_path) # 预处理数据 self.preprocess_data() print("边坡温度分析系统初始化完成") print(f"结果将保存到: {self.output_dir}") print(f"切片位置设置 - XY平面Z坐标: {self.xy_slice_z}mm, XZ平面Y坐标: {self.xz_slice_y}mm, YZ平面X坐标: {self.yz_slice_x}mm") def check_files_exist(self, temp_path, coord_path, stl_path): """检查必要的文件是否存在""" if not os.path.exists(temp_path): print(f"警告: 温度数据文件不存在: {temp_path}") if not os.path.exists(coord_path): print(f"警告: 坐标数据文件不存在: {coord_path}") if not os.path.exists(stl_path): print(f"警告: STL模型文件不存在: {stl_path}") def set_chinese_font(self): """设置中文字体支持""" try: plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;, &#39;Microsoft YaHei&#39;, &#39;DejaVu Sans&#39;] plt.rcParams[&#39;axes.unicode_minus&#39;] = False except: plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;DejaVu Sans&#39;] def load_data(self, temp_data_file, coord_data_file): """ 从Excel文件加载温度数据和坐标数据 """ print("正在从Excel文件加载数据...") try: # 加载温度数据 self.temp_df = pd.read_excel(temp_data_file) print(f"温度数据记录数: {len(self.temp_df)}") print(f"温度数据列名: {list(self.temp_df.columns)}") # 加载坐标数据 self.coord_df = pd.read_excel(coord_data_file) print(f"测点数量: {len(self.coord_df)}") print(f"坐标数据列名: {list(self.coord_df.columns)}") except Exception as e: print(f"Excel文件读取失败: {e}") print("尝试使用CSV格式...") # 如果Excel读取失败,尝试CSV格式 self.temp_df = pd.read_csv(temp_data_file) self.coord_df = pd.read_csv(coord_data_file) # 检查并标准化列名 self.standardize_column_names() # 处理重复列名 self.handle_duplicate_columns() # 合并数据 self.merged_df = pd.merge(self.temp_df, self.coord_df, on=&#39;测点编号&#39;) print(f"合并后数据记录数: {len(self.merged_df)}") # 转换时间格式 if &#39;时间&#39; in self.merged_df.columns: self.merged_df[&#39;时间&#39;] = pd.to_datetime(self.merged_df[&#39;时间&#39;]) elif &#39;日期&#39; in self.merged_df.columns: self.merged_df[&#39;时间&#39;] = pd.to_datetime(self.merged_df[&#39;日期&#39;]) print("使用&#39;日期&#39;列作为时间列") def standardize_column_names(self): """标准化列名""" # 温度数据列名映射 temp_column_mapping = {} for col in self.temp_df.columns: col_lower = str(col).lower() if any(keyword in col_lower for keyword in [&#39;测点&#39;, &#39;点号&#39;, &#39;point&#39;]): temp_column_mapping[col] = &#39;测点编号&#39; elif any(keyword in col_lower for keyword in [&#39;时间&#39;, &#39;日期&#39;, &#39;time&#39;, &#39;date&#39;]): temp_column_mapping[col] = &#39;时间&#39; elif any(keyword in col_lower for keyword in [&#39;温度&#39;, &#39;temp&#39;]): temp_column_mapping[col] = &#39;温度&#39; if temp_column_mapping: self.temp_df = self.temp_df.rename(columns=temp_column_mapping) print(f"温度数据列名标准化: {temp_column_mapping}") # 坐标数据列名映射 coord_column_mapping = {} for col in self.coord_df.columns: col_lower = str(col).lower() if any(keyword in col_lower for keyword in [&#39;测点&#39;, &#39;点号&#39;, &#39;point&#39;]): coord_column_mapping[col] = &#39;测点编号&#39; elif any(keyword in col_lower for keyword in [&#39;x&#39;, &#39;坐标x&#39;, &#39;coordx&#39;]): coord_column_mapping[col] = &#39;X坐标&#39; elif any(keyword in col_lower for keyword in [&#39;y&#39;, &#39;坐标y&#39;, &#39;coordy&#39;]): coord_column_mapping[col] = &#39;Y坐标&#39; elif any(keyword in col_lower for keyword in [&#39;高程&#39;, &#39;海拔&#39;, &#39;z&#39;, &#39;坐标z&#39;, &#39;coordz&#39;, &#39;elevation&#39;]): coord_column_mapping[col] = &#39;高程&#39; if coord_column_mapping: self.coord_df = self.coord_df.rename(columns=coord_column_mapping) print(f"坐标数据列名标准化: {coord_column_mapping}") # 检查必要的列是否存在 required_temp_cols = [&#39;测点编号&#39;, &#39;时间&#39;, &#39;温度&#39;] required_coord_cols = [&#39;测点编号&#39;, &#39;X坐标&#39;, &#39;Y坐标&#39;, &#39;高程&#39;] missing_temp = [col for col in required_temp_cols if col not in self.temp_df.columns] missing_coord = [col for col in required_coord_cols if col not in self.coord_df.columns] if missing_temp: print(f"警告: 温度数据缺少必要的列: {missing_temp}") if missing_coord: print(f"警告: 坐标数据缺少必要的列: {missing_coord}") def handle_duplicate_columns(self): """处理重复的列名""" # 检查温度数据中的重复列名 temp_duplicates = self.temp_df.columns.duplicated() if temp_duplicates.any(): print(f"温度数据中有重复列名: {list(self.temp_df.columns[temp_duplicates])}") # 删除重复列,保留第一个 self.temp_df = self.temp_df.loc[:, ~temp_duplicates] print("已删除温度数据中的重复列") # 检查坐标数据中的重复列名 coord_duplicates = self.coord_df.columns.duplicated() if coord_duplicates.any(): print(f"坐标数据中有重复列名: {list(self.coord_df.columns[coord_duplicates])}") # 删除重复列,保留第一个 self.coord_df = self.coord_df.loc[:, ~coord_duplicates] print("已删除坐标数据中的重复列") # 打印最终列名 print(f"温度数据最终列名: {list(self.temp_df.columns)}") print(f"坐标数据最终列名: {list(self.coord_df.columns)}") def load_slope_model(self, stl_file): """ 从STL文件加载边坡三维模型 """ print("正在从STL文件加载边坡三维模型...") try: # 使用trimesh加载STL文件 self.slope_mesh = trimesh.load_mesh(stl_file) print(f"STL文件加载成功:") print(f"顶点数: {len(self.slope_mesh.vertices)}") print(f"面片数: {len(self.slope_mesh.faces)}") print(f"模型边界: {self.slope_mesh.bounds}") # 获取模型的几何中心 self.mesh_center = self.slope_mesh.centroid print(f"模型中心: {self.mesh_center}") # 检查模型是否是水密的(封闭的) self.is_watertight = self.slope_mesh.is_watertight print(f"模型是否水密: {self.is_watertight}") except Exception as e: print(f"STL文件读取失败: {e}") print("创建基于测点坐标的简化边坡模型...") self.create_simplified_slope_model() def create_simplified_slope_model(self): """创建基于测点坐标的简化边坡模型""" print("创建基于测点坐标的简化边坡模型...") # 使用测点坐标创建凸包作为简化模型 points = self.coord_df[[&#39;X坐标&#39;, &#39;Y坐标&#39;, &#39;高程&#39;]].values self.slope_mesh = trimesh.convex.convex_hull(points) self.is_watertight = self.slope_mesh.is_watertight print(f"简化模型边界: {self.slope_mesh.bounds}") def is_point_in_mesh(self, points, tolerance=0.1): """ 判断点是否在网格模型内部或边界附近 参数: points: (n, 3) 形状的点坐标数组 tolerance: 边界容差(mm),用于保留边界附近的点 返回: inside: (n,) 形状的布尔数组,True表示点在模型内部或边界附近 """ if not hasattr(self, &#39;slope_mesh&#39;) or self.slope_mesh is None: # 如果没有模型,认为所有点都在范围内 return np.ones(len(points), dtype=bool) try: # 使用射线法判断点是否在模型内部 if self.is_watertight: inside = self.slope_mesh.contains(points) # 对于不在内部的点,检查是否在边界附近 if not np.all(inside): # 计算到模型表面的距离 distances = self.slope_mesh.nearest.on_surface(points[~inside])[1] # 如果距离小于容差,则认为在边界上 on_boundary = distances < tolerance inside[~inside] = on_boundary else: # 如果模型不是水密的,使用近似方法:判断点是否在模型边界框内 bounds = self.slope_mesh.bounds inside = np.all((points >= bounds[0]) & (points <= bounds[1]), axis=1) return inside except Exception as e: print(f"点包含性检测失败: {e}") # 失败时返回所有点都在范围内 return np.ones(len(points), dtype=bool) def preprocess_data(self): """ 数据预处理 """ print("正在预处理数据...") # 提取唯一时间点(包括日期和时间) if &#39;时间&#39; in self.merged_df.columns: # 提取唯一的时间点 self.time_points = sorted(self.merged_df[&#39;时间&#39;].unique()) print(f"数据覆盖时间点: {len(self.time_points)}") # 同时提取日期用于分组 self.merged_df[&#39;日期&#39;] = self.merged_df[&#39;时间&#39;].dt.date self.dates = sorted(self.merged_df[&#39;日期&#39;].unique()) print(f"数据覆盖天数: {len(self.dates)}") else: print("警告: 未找到时间列,使用默认日期") self.time_points = [datetime.now()] self.dates = [datetime.now().date()] # 提取测点信息 self.monitoring_points = self.merged_df[[&#39;测点编号&#39;, &#39;X坐标&#39;, &#39;Y坐标&#39;, &#39;高程&#39;]].drop_duplicates() print(f"监测点数量: {len(self.monitoring_points)}") # 创建插值网格(只在模型范围内) self.create_interpolation_grid() def create_interpolation_grid(self): """ 创建温度插值网格 - 只在STL模型范围内创建网格点 """ # 如果有STL模型,使用模型边界 if hasattr(self, &#39;slope_mesh&#39;) and self.slope_mesh is not None: bounds = self.slope_mesh.bounds min_x, max_x = bounds[0][0], bounds[1][0] min_y, max_y = bounds[0][1], bounds[1][1] min_z, max_z = bounds[0][2], bounds[1][2] else: # 否则使用测点坐标范围 min_x, max_x = self.coord_df[&#39;X坐标&#39;].min(), self.coord_df[&#39;X坐标&#39;].max() min_y, max_y = self.coord_df[&#39;Y坐标&#39;].min(), self.coord_df[&#39;Y坐标&#39;].max() min_z, max_z = self.coord_df[&#39;高程&#39;].min(), self.coord_df[&#39;高程&#39;].max() # 创建密集的初始网格 grid_points = 50 # 将网格数组保存为实例属性 self.x_grid = np.linspace(min_x, max_x, grid_points) self.y_grid = np.linspace(min_y, max_y, grid_points) self.z_grid = np.linspace(min_z, max_z, grid_points // 2) # 创建完整的网格 self.X_grid, self.Y_grid, self.Z_grid = np.meshgrid( self.x_grid, self.y_grid, self.z_grid, indexing=&#39;ij&#39; ) # 将网格点展平以进行包含性检测 grid_points_flat = np.vstack([ self.X_grid.ravel(), self.Y_grid.ravel(), self.Z_grid.ravel() ]).T # 检测哪些点在模型内部或边界附近 print("正在检测网格点在模型内部的情况...") self.inside_mask = self.is_point_in_mesh(grid_points_flat, tolerance=0.5) inside_ratio = np.sum(self.inside_mask) / len(self.inside_mask) print(f"模型内部网格点比例: {inside_ratio:.2%}") print(f"有效网格点数: {np.sum(self.inside_mask)}") print(f"插值网格尺寸: {self.X_grid.shape}") print(f"网格范围: X({min_x:.1f}-{max_x:.1f}mm), Y({min_y:.1f}-{max_y:.1f}mm), Z({min_z:.1f}-{max_z:.1f}mm)") def format_time_point(self, time_point): """ 格式化时间点,处理numpy.datetime64和datetime对象 格式: 月-日 小时 (省略年份,精确到小时) """ if isinstance(time_point, np.datetime64): # 将numpy.datetime64转换为pandas.Timestamp time_point = pd.Timestamp(time_point) if hasattr(time_point, &#39;strftime&#39;): # 格式化为 "月-日 小时" 格式,例如 "07-29 15" return time_point.strftime("%m-%d %H") else: return str(time_point) def interpolate_temperature_3d(self, time_point): """ 三维温度场插值 - 只在模型范围内插值 参数: time_point: 时间点 返回: 三维温度场(模型外部的点为NaN) """ # 获取指定时间点的数据 time_data = self.merged_df[self.merged_df[&#39;时间&#39;] == time_point] if len(time_data) == 0: print(f"警告: 时间点 {time_point} 无数据") return None # 提取测点坐标和温度 points = time_data[[&#39;X坐标&#39;, &#39;Y坐标&#39;, &#39;高程&#39;]].values temperatures = time_data[&#39;温度&#39;].values print(f"插值使用测点数: {len(points)}") # 使用径向基函数插值获得更平滑的结果 try: # 尝试使用RBF插值 rbf = Rbf(points[:, 0], points[:, 1], points[:, 2], temperatures, function=&#39;linear&#39;, smooth=0.1) # 在网格上计算温度 grid_temps = rbf(self.X_grid, self.Y_grid, self.Z_grid) # 应用高斯滤波平滑结果 grid_temps = gaussian_filter(grid_temps, sigma=0.5) except Exception as e: print(f"RBF插值失败: {e},使用线性插值") # 如果RBF失败,使用线性插值 grid_temps = griddata( points, temperatures, (self.X_grid, self.Y_grid, self.Z_grid), method=&#39;linear&#39;, fill_value=np.nan ) # 将模型外部的温度设为NaN grid_temps_flat = grid_temps.ravel() grid_temps_flat[~self.inside_mask] = np.nan grid_temps = grid_temps_flat.reshape(grid_temps.shape) return grid_temps def create_time_temperature_contour(self, time_point): """ 创建每个时间点的温度场等值线云图 """ time_str = self.format_time_point(time_point) print(f"正在生成 {time_str} 的温度场等值线云图...") # 获取温度场数据 temp_field = self.interpolate_temperature_3d(time_point) if temp_field is None: return # 创建可视化 fig = plt.figure(figsize=(18, 12)) # 1. 三维等值面图 ax1 = fig.add_subplot(231, projection=&#39;3d&#39;) self.plot_3d_isosurface(ax1, temp_field, time_point) # 2. 三维切片图 ax2 = fig.add_subplot(232, projection=&#39;3d&#39;) self.plot_3d_slices(ax2, temp_field, time_point) # 3. XY平面温度等值线 ax3 = fig.add_subplot(233) self.plot_xy_contour(ax3, temp_field, time_point) # 4. XZ平面温度等值线 ax4 = fig.add_subplot(234) self.plot_xz_contour(ax4, temp_field, time_point) # 5. YZ平面温度等值线 ax5 = fig.add_subplot(235) self.plot_yz_contour(ax5, temp_field, time_point) # 6. 温度分布直方图 ax6 = fig.add_subplot(236) self.plot_temperature_histogram(ax6, temp_field, time_point) plt.tight_layout() # 保存图片 filename = f"温度场等值线_{self.format_time_point(time_point).replace(&#39;:&#39;, &#39;&#39;).replace(&#39; &#39;, &#39;_&#39;)}.png" filepath = os.path.join(self.output_dir, filename) plt.savefig(filepath, dpi=300, bbox_inches=&#39;tight&#39;) plt.close() print(f"已保存: {filename}") def plot_3d_isosurface(self, ax, temp_field, time_point): """ 绘制三维等值面图 - 只在模型范围内绘制,网格边界完全透明 """ try: from mpl_toolkits.mplot3d.art3d import Poly3DCollection from skimage import measure # 创建掩码,只处理模型内部的点 valid_mask = ~np.isnan(temp_field) if np.sum(valid_mask) == 0: print("警告: 没有有效的温度数据用于等值面生成") return # 计算等值面水平 valid_temps = temp_field[valid_mask] temp_min = np.nanmin(valid_temps) temp_max = np.nanmax(valid_temps) levels = np.linspace(temp_min, temp_max, 6) # 创建用于marching cubes的数组(将NaN设为很小的值) temp_field_mc = np.copy(temp_field) temp_field_mc[np.isnan(temp_field_mc)] = temp_min - 10 # 绘制等值面 for i, level in enumerate(levels[1:-1]): # 跳过最低和最高值 try: # 计算网格间距 dx = self.x_grid[1] - self.x_grid[0] if len(self.x_grid) > 1 else 1.0 dy = self.y_grid[1] - self.y_grid[0] if len(self.y_grid) > 1 else 1.0 dz = self.z_grid[1] - self.z_grid[0] if len(self.z_grid) > 1 else 1.0 # 使用 marching cubes 算法提取等值面 verts, faces, _, _ = measure.marching_cubes( temp_field_mc, level=level, spacing=(dx, dy, dz) ) # 转换坐标到实际位置 verts[:, 0] = verts[:, 0] + self.x_grid[0] verts[:, 1] = verts[:, 1] + self.y_grid[0] verts[:, 2] = verts[:, 2] + self.z_grid[0] # 创建边形集合 - 设置网格边界完全透明 mesh = Poly3DCollection(verts[faces], alpha=0.3, linewidth=0) # 根据温度值设置颜色 normalized_temp = (level - temp_min) / (temp_max - temp_min) color = plt.cm.jet(normalized_temp) mesh.set_facecolor(color) mesh.set_edgecolor(&#39;none&#39;) # 设置边缘颜色为完全透明 ax.add_collection3d(mesh) except Exception as e: print(f"等值面 {level:.1f}°C 生成失败: {e}") continue except ImportError: print("skimage不可用,跳过等值面生成") except Exception as e: print(f"等值面生成失败: {e}") # 绘制监测点 time_data = self.merged_df[self.merged_df[&#39;时间&#39;] == time_point] scatter = ax.scatter(time_data[&#39;X坐标&#39;], time_data[&#39;Y坐标&#39;], time_data[&#39;高程&#39;], c=time_data[&#39;温度&#39;], cmap=&#39;jet&#39;, s=50, edgecolors=&#39;black&#39;, linewidth=1, alpha=0.8) # 设置坐标轴范围,精确匹配模型范围 if hasattr(self, &#39;slope_mesh&#39;) and self.slope_mesh is not None: bounds = self.slope_mesh.bounds ax.set_xlim(bounds[0][0], bounds[1][0]) ax.set_ylim(bounds[0][1], bounds[1][1]) ax.set_zlim(bounds[0][2], bounds[1][2]) ax.set_xlabel(&#39;X坐标 (mm)&#39;, fontsize=10) ax.set_ylabel(&#39;Y坐标 (mm)&#39;, fontsize=10) ax.set_zlabel(&#39;高程 (mm)&#39;, fontsize=10) ax.set_title(f&#39;三维温度等值面\n{self.format_time_point(time_point)}&#39;, fontsize=12) # 添加颜色条 plt.colorbar(scatter, ax=ax, shrink=0.6, label=&#39;温度 (°C)&#39;) def plot_3d_slices(self, ax, temp_field, time_point): """ 绘制三维切片图 - 只在模型范围内绘制,网格边界完全透明 """ # 选择几个切片位置 x_slice_idx = len(self.x_grid) // 2 y_slice_idx = len(self.y_grid) // 2 z_slice_idx = len(self.z_grid) // 3 # 创建颜色归一化 valid_temps = temp_field[~np.isnan(temp_field)] if len(valid_temps) == 0: print("警告: 没有有效的温度数据") return temp_min = np.min(valid_temps) temp_max = np.max(valid_temps) norm = plt.Normalize(temp_min, temp_max) # X方向切片 xx, yy = np.meshgrid(self.y_grid, self.z_grid) zz = np.ones_like(xx) * self.x_grid[x_slice_idx] slice_temp_x = temp_field[x_slice_idx, :, :].T # 只绘制有效数据 valid_x = ~np.isnan(slice_temp_x) if np.any(valid_x): colors_x = plt.cm.jet(norm(slice_temp_x)) # 将无效数据设为透明 colors_x[~valid_x] = [0, 0, 0, 0] surf_x = ax.plot_surface(zz, xx, yy, facecolors=colors_x, alpha=0.6, rstride=2, cstride=2, linewidth=0, edgecolor=&#39;none&#39;) # Y方向切片 xx, zz = np.meshgrid(self.x_grid, self.z_grid) yy = np.ones_like(xx) * self.y_grid[y_slice_idx] slice_temp_y = temp_field[:, y_slice_idx, :].T valid_y = ~np.isnan(slice_temp_y) if np.any(valid_y): colors_y = plt.cm.jet(norm(slice_temp_y)) colors_y[~valid_y] = [0, 0, 0, 0] surf_y = ax.plot_surface(xx, yy, zz, facecolors=colors_y, alpha=0.6, rstride=2, cstride=2, linewidth=0, edgecolor=&#39;none&#39;) # Z方向切片(地表) xx, yy = np.meshgrid(self.x_grid, self.y_grid) zz = np.ones_like(xx) * self.z_grid[z_slice_idx] slice_temp_z = temp_field[:, :, z_slice_idx].T valid_z = ~np.isnan(slice_temp_z) if np.any(valid_z): colors_z = plt.cm.jet(norm(slice_temp_z)) colors_z[~valid_z] = [0, 0, 0, 0] surf_z = ax.plot_surface(xx, yy, zz, facecolors=colors_z, alpha=0.7, rstride=2, cstride=2, linewidth=0, edgecolor=&#39;none&#39;) # 绘制监测点 time_data = self.merged_df[self.merged_df[&#39;时间&#39;] == time_point] scatter = ax.scatter(time_data[&#39;X坐标&#39;], time_data[&#39;Y坐标&#39;], time_data[&#39;高程&#39;], c=time_data[&#39;温度&#39;], cmap=&#39;jet&#39;, s=50, edgecolors=&#39;white&#39;, linewidth=1) # 设置坐标轴范围,精确匹配模型范围 if hasattr(self, &#39;slope_mesh&#39;) and self.slope_mesh is not None: bounds = self.slope_mesh.bounds ax.set_xlim(bounds[0][0], bounds[1][0]) ax.set_ylim(bounds[0][1], bounds[1][1]) ax.set_zlim(bounds[0][2], bounds[1][2]) ax.set_xlabel(&#39;X坐标 (mm)&#39;, fontsize=10) ax.set_ylabel(&#39;Y坐标 (mm)&#39;, fontsize=10) ax.set_zlabel(&#39;高程 (mm)&#39;, fontsize=10) ax.set_title(f&#39;三维温度切片\n{self.format_time_point(time_point)}&#39;, fontsize=12) # 添加颜色条 plt.colorbar(scatter, ax=ax, shrink=0.6, label=&#39;温度 (°C)&#39;) def plot_xy_contour(self, ax, temp_field, time_point): """ 绘制XY平面温度等值线 - 只在模型范围内绘制 """ # 确定切片位置 if self.xy_slice_z is not None: # 使用用户指定的Z坐标 z_idx = np.argmin(np.abs(self.z_grid - self.xy_slice_z)) slice_z = self.xy_slice_z else: # 使用平均高度 z_idx = len(self.z_grid) // 2 slice_z = self.z_grid[z_idx] slice_temp = temp_field[:, :, z_idx] # 创建等值线图(只绘制有效数据) valid_mask = ~np.isnan(slice_temp) if np.any(valid_mask): contour = ax.contourf(self.x_grid, self.y_grid, slice_temp.T, levels=20, cmap=&#39;jet&#39;, alpha=0.8) # 添加等值线 contour_lines = ax.contour(self.x_grid, self.y_grid, slice_temp.T, levels=10, colors=&#39;black&#39;, linewidths=0.5, alpha=0.7) ax.clabel(contour_lines, inline=True, fontsize=8, fmt=&#39;%.1f°C&#39;) # 添加监测点 time_data = self.merged_df[self.merged_df[&#39;时间&#39;] == time_point] scatter = ax.scatter(time_data[&#39;X坐标&#39;], time_data[&#39;Y坐标&#39;], c=time_data[&#39;温度&#39;], cmap=&#39;jet&#39;, s=50, edgecolors=&#39;white&#39;, linewidth=1) # 设置坐标轴范围,精确匹配模型范围 if hasattr(self, &#39;slope_mesh&#39;) and self.slope_mesh is not None: bounds = self.slope_mesh.bounds ax.set_xlim(bounds[0][0], bounds[1][0]) ax.set_ylim(bounds[0][1], bounds[1][1]) ax.set_xlabel(&#39;X坐标 (mm)&#39;, fontsize=10) ax.set_ylabel(&#39;Y坐标 (mm)&#39;, fontsize=10) ax.set_title(f&#39;XY平面温度等值线\n高程: {slice_z:.1f}mm\n{self.format_time_point(time_point)}&#39;, fontsize=12) ax.grid(True, alpha=0.3) if np.any(valid_mask): plt.colorbar(contour, ax=ax, label=&#39;温度 (°C)&#39;) def plot_xz_contour(self, ax, temp_field, time_point): """ 绘制XZ平面温度等值线 - 只在模型范围内绘制 """ # 确定切片位置 if self.xz_slice_y is not None: # 使用用户指定的Y坐标 y_idx = np.argmin(np.abs(self.y_grid - self.xz_slice_y)) slice_y = self.xz_slice_y else: # 使用中心位置 y_idx = len(self.y_grid) // 2 slice_y = self.y_grid[y_idx] slice_temp = temp_field[:, y_idx, :] # 创建等值线图(只绘制有效数据) valid_mask = ~np.isnan(slice_temp) if np.any(valid_mask): contour = ax.contourf(self.x_grid, self.z_grid, slice_temp.T, levels=20, cmap=&#39;jet&#39;, alpha=0.8) # 添加等值线 contour_lines = ax.contour(self.x_grid, self.z_grid, slice_temp.T, levels=10, colors=&#39;black&#39;, linewidths=0.5, alpha=0.7) ax.clabel(contour_lines, inline=True, fontsize=8, fmt=&#39;%.1f°C&#39;) # 设置坐标轴范围,精确匹配模型范围 if hasattr(self, &#39;slope_mesh&#39;) and self.slope_mesh is not None: bounds = self.slope_mesh.bounds ax.set_xlim(bounds[0][0], bounds[1][0]) ax.set_ylim(bounds[0][2], bounds[1][2]) ax.set_xlabel(&#39;X坐标 (mm)&#39;, fontsize=10) ax.set_ylabel(&#39;高程 (mm)&#39;, fontsize=10) ax.set_title(f&#39;XZ平面温度等值线\nY: {slice_y:.1f}mm\n{self.format_time_point(time_point)}&#39;, fontsize=12) ax.grid(True, alpha=0.3) if np.any(valid_mask): plt.colorbar(contour, ax=ax, label=&#39;温度 (°C)&#39;) def plot_yz_contour(self, ax, temp_field, time_point): """ 绘制YZ平面温度等值线 - 只在模型范围内绘制 """ # 确定切片位置 if self.yz_slice_x is not None: # 使用用户指定的X坐标 x_idx = np.argmin(np.abs(self.x_grid - self.yz_slice_x)) slice_x = self.yz_slice_x else: # 使用中心位置 x_idx = len(self.x_grid) // 2 slice_x = self.x_grid[x_idx] slice_temp = temp_field[x_idx, :, :] # 创建等值线图(只绘制有效数据) valid_mask = ~np.isnan(slice_temp) if np.any(valid_mask): contour = ax.contourf(self.y_grid, self.z_grid, slice_temp.T, levels=20, cmap=&#39;jet&#39;, alpha=0.8) # 添加等值线 contour_lines = ax.contour(self.y_grid, self.z_grid, slice_temp.T, levels=10, colors=&#39;black&#39;, linewidths=0.5, alpha=0.7) ax.clabel(contour_lines, inline=True, fontsize=8, fmt=&#39;%.1f°C&#39;) # 设置坐标轴范围,精确匹配模型范围 if hasattr(self, &#39;slope_mesh&#39;) and self.slope_mesh is not None: bounds = self.slope_mesh.bounds ax.set_xlim(bounds[0][1], bounds[1][1]) ax.set_ylim(bounds[0][2], bounds[1][2]) ax.set_xlabel(&#39;Y坐标 (mm)&#39;, fontsize=10) ax.set_ylabel(&#39;高程 (mm)&#39;, fontsize=10) ax.set_title(f&#39;YZ平面温度等值线\nX: {slice_x:.1f}mm\n{self.format_time_point(time_point)}&#39;, fontsize=12) ax.grid(True, alpha=0.3) if np.any(valid_mask): plt.colorbar(contour, ax=ax, label=&#39;温度 (°C)&#39;) def plot_temperature_histogram(self, ax, temp_field, time_point): """ 绘制温度分布直方图 """ # 获取温度数据 time_data = self.merged_df[self.merged_df[&#39;时间&#39;] == time_point] measured_temps = time_data[&#39;温度&#39;].values interpolated_temps = temp_field[~np.isnan(temp_field)] # 绘制直方图 ax.hist(measured_temps, bins=15, alpha=0.7, color=&#39;blue&#39;, label=f&#39;实测温度 (n={len(measured_temps)})&#39;, density=True) ax.hist(interpolated_temps, bins=30, alpha=0.5, color=&#39;red&#39;, label=f&#39;插值温度 (n={len(interpolated_temps)})&#39;, density=True) ax.set_xlabel(&#39;温度 (°C)&#39;, fontsize=10) ax.set_ylabel(&#39;频率密度&#39;, fontsize=10) ax.set_title(f&#39;温度分布直方图\n{self.format_time_point(time_point)}&#39;, fontsize=12) ax.legend(fontsize=9) ax.grid(True, alpha=0.3) def run_analysis(self): """ 运行完整的温度场分析 """ print("开始边坡温度场分析...") # 1. 生成每个时间点的温度场等值线云图 print("\n1. 生成每个时间点的温度场等值线云图") for time_point in self.time_points: self.create_time_temperature_contour(time_point) # 2. 生成不同深度温度分析 print("\n2. 生成不同深度温度场分析") self.create_depth_temperature_analysis() # 3. 生成温度时间序列 print("\n3. 生成温度时间序列分析") self.generate_temperature_time_series() # 4. 生成分析报告 print("\n4. 生成分析报告") self.generate_analysis_report() print(f"\n分析完成!所有结果已保存到: {self.output_dir}") def create_depth_temperature_analysis(self): """ 创建不同深度的温度场分析 - 使用等值线 """ print("正在生成不同深度的温度场分析...") # 选择几个关键深度 depths = [self.z_grid[0], self.z_grid[len(self.z_grid)//4], self.z_grid[len(self.z_grid)//2], self.z_grid[3*len(self.z_grid)//4], self.z_grid[-1]] # 选择几个关键时间点 num_times = min(3, len(self.time_points)) selected_times = [self.time_points[i * len(self.time_points) // num_times] for i in range(num_times)] # 为每个深度创建等值线图 for depth in depths: fig, axes = plt.subplots(1, num_times, figsize=(6*num_times, 5)) if num_times == 1: axes = [axes] depth_index = np.argmin(np.abs(self.z_grid - depth)) for i, time_point in enumerate(selected_times): temp_field = self.interpolate_temperature_3d(time_point) if temp_field is None: continue # 在该深度的XY切片 slice_temp = temp_field[:, :, depth_index] # 绘制等值线图(只绘制有效数据) valid_mask = ~np.isnan(slice_temp) if np.any(valid_mask): contour = axes[i].contourf(self.x_grid, self.y_grid, slice_temp.T, levels=20, cmap=&#39;jet&#39;) # 添加等值线 contour_lines = axes[i].contour(self.x_grid, self.y_grid, slice_temp.T, levels=10, colors=&#39;black&#39;, linewidths=0.5, alpha=0.7) axes[i].clabel(contour_lines, inline=True, fontsize=6, fmt=&#39;%.1f°C&#39;) # 添加监测点 time_data = self.merged_df[self.merged_df[&#39;时间&#39;] == time_point] scatter = axes[i].scatter(time_data[&#39;X坐标&#39;], time_data[&#39;Y坐标&#39;], c=time_data[&#39;温度&#39;], cmap=&#39;jet&#39;, s=30, edgecolors=&#39;white&#39;, linewidth=0.5) # 设置坐标轴范围,精确匹配模型范围 if hasattr(self, &#39;slope_mesh&#39;) and self.slope_mesh is not None: bounds = self.slope_mesh.bounds axes[i].set_xlim(bounds[0][0], bounds[1][0]) axes[i].set_ylim(bounds[0][1], bounds[1][1]) axes[i].set_xlabel(&#39;X坐标 (mm)&#39;, fontsize=9) axes[i].set_ylabel(&#39;Y坐标 (mm)&#39;, fontsize=9) # 使用新的时间格式 formatted_time = self.format_time_point(time_point) axes[i].set_title(f&#39;{formatted_time}\n深度: {depth:.1f}mm&#39;, fontsize=10) axes[i].grid(True, alpha=0.3) plt.tight_layout() # 添加共享颜色条 if np.any(valid_mask): cbar = fig.colorbar(contour, ax=axes, shrink=0.8, orientation=&#39;horizontal&#39;, pad=0.05, label=&#39;温度 (°C)&#39;) # 保存图片 filename = f"深度_{depth:.1f}mm_温度等值线.png" filepath = os.path.join(self.output_dir, filename) plt.savefig(filepath, dpi=300, bbox_inches=&#39;tight&#39;) plt.close() print(f"已保存: {filename}") def generate_temperature_time_series(self): """ 生成温度时间序列分析 - 使用月-日 小时格式 """ print("正在生成温度时间序列分析...") # 为每个测点创建温度时间序列 fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10)) # 1. 所有测点的温度时间序列 for point_id in self.monitoring_points[&#39;测点编号&#39;].unique(): point_data = self.merged_df[self.merged_df[&#39;测点编号&#39;] == point_id] point_data = point_data.sort_values(&#39;时间&#39;) # 格式化时间为月-日 小时格式 formatted_times = point_data[&#39;时间&#39;].apply(lambda x: self.format_time_point(x)) ax1.plot(formatted_times, point_data[&#39;温度&#39;], label=f&#39;测点{point_id}&#39;, linewidth=1, alpha=0.7) ax1.set_xlabel(&#39;时间 (月-日 小时)&#39;, fontsize=12) ax1.set_ylabel(&#39;温度 (°C)&#39;, fontsize=12) ax1.set_title(&#39;各测点温度时间序列&#39;, fontsize=14) ax1.legend(bbox_to_anchor=(1.05, 1), loc=&#39;upper left&#39;) ax1.grid(True, alpha=0.3) # 如果时间点太,旋转x轴标签避免重叠 if len(self.time_points) > 10: plt.setp(ax1.get_xticklabels(), rotation=45, ha=&#39;right&#39;) # 2. 平均温度时间序列 if &#39;时间&#39; in self.merged_df.columns: # 按时间点计算平均温度 time_avg = self.merged_df.groupby(&#39;时间&#39;)[&#39;温度&#39;].mean() # 格式化时间为月-日 小时格式 formatted_times = [self.format_time_point(t) for t in time_avg.index] ax2.plot(formatted_times, time_avg.values, &#39;ro-&#39;, linewidth=2, markersize=4) ax2.set_xlabel(&#39;时间 (月-日 小时)&#39;, fontsize=12) ax2.set_ylabel(&#39;平均温度 (°C)&#39;, fontsize=12) ax2.set_title(&#39;时间点平均温度变化&#39;, fontsize=14) ax2.grid(True, alpha=0.3) # 如果时间点太,旋转x轴标签避免重叠 if len(time_avg) > 10: plt.setp(ax2.get_xticklabels(), rotation=45, ha=&#39;right&#39;) plt.tight_layout() # 保存图片 filename = "温度时间序列分析.png" filepath = os.path.join(self.output_dir, filename) plt.savefig(filepath, dpi=300, bbox_inches=&#39;tight&#39;) plt.close() print(f"已保存: {filename}") def generate_analysis_report(self): """ 生成分析报告 """ # 基本统计信息 temp_stats = { &#39;最高温度&#39;: self.merged_df[&#39;温度&#39;].max(), &#39;最低温度&#39;: self.merged_df[&#39;温度&#39;].min(), &#39;平均温度&#39;: self.merged_df[&#39;温度&#39;].mean(), &#39;温度标准差&#39;: self.merged_df[&#39;温度&#39;].std() } coord_stats = { &#39;X坐标范围&#39;: f"{self.coord_df[&#39;X坐标&#39;].min():.1f} - {self.coord_df[&#39;X坐标&#39;].max():.1f} mm", &#39;Y坐标范围&#39;: f"{self.coord_df[&#39;Y坐标&#39;].min():.1f} - {self.coord_df[&#39;Y坐标&#39;].max():.1f} mm", &#39;高程范围&#39;: f"{self.coord_df[&#39;高程&#39;].min():.1f} - {self.coord_df[&#39;高程&#39;].max():.1f} mm" } if hasattr(self, &#39;slope_mesh&#39;) and self.slope_mesh is not None: mesh_info = f""" 模型信息: - 顶点数: {len(self.slope_mesh.vertices)} - 面片数: {len(self.slope_mesh.faces)} - 模型边界: {self.slope_mesh.bounds} - 模型是否水密: {self.is_watertight} """ else: mesh_info = "- 模型: 基于测点创建的简化模型" report_content = f""" 边坡温度场分析报告 生成时间: {datetime.now().strftime(&#39;%Y-%m-%d %H:%M:%S&#39;)} 数据概况: - 数据时间范围: {self.format_time_point(self.time_points[0]) if self.time_points else &#39;未知&#39;} 至 {self.format_time_point(self.time_points[-1]) if self.time_points else &#39;未知&#39;} - 总时间点: {len(self.time_points)} - 监测点数量: {len(self.monitoring_points)} - 温度记录数: {len(self.merged_df)} 温度统计: - 最高温度: {temp_stats[&#39;最高温度&#39;]:.2f} °C - 最低温度: {temp_stats[&#39;最低温度&#39;]:.2f} °C - 平均温度: {temp_stats[&#39;平均温度&#39;]:.2f} °C - 温度标准差: {temp_stats[&#39;温度标准差&#39;]:.2f} °C 坐标范围: - X坐标: {coord_stats[&#39;X坐标范围&#39;]} - Y坐标: {coord_stats[&#39;Y坐标范围&#39;]} - 高程: {coord_stats[&#39;高程范围&#39;]} 切片位置设置: - XY平面Z坐标: {self.xy_slice_z if self.xy_slice_z is not None else &#39;自动(平均高度)&#39;} mm - XZ平面Y坐标: {self.xz_slice_y if self.xz_slice_y is not None else &#39;自动(中心位置)&#39;} mm - YZ平面X坐标: {self.yz_slice_x if self.yz_slice_x is not None else &#39;自动(中心位置)&#39;} mm {mesh_info} 生成文件说明: 1. 温度场等值线_YYYYMMDD_HHMMSS.png - 每个时间点的视图温度场等值线图 2. 深度_XX.Xmm_温度等值线.png - 不同深度的温度等值线 3. 温度时间序列分析.png - 测点温度变化趋势 文件保存位置: {self.output_dir} """ report_path = os.path.join(self.output_dir, "分析报告.txt") with open(report_path, &#39;w&#39;, encoding=&#39;utf-8&#39;) as f: f.write(report_content) print("已保存: 分析报告.txt") # ========================= 示例使用 ========================= if __name__ == "__main__": # 使用示例 - 只需要提供文件名,完整路径会自动构建 # 可以自定义切片位置(单位:mm) analyzer = SlopeTemperatureAnalysis( temp_data_file=&#39;温度数据.xlsx&#39;, # 文件位于 D:\桌面\温度数据\三维温度图\温度数据.xlsx coord_data_file=&#39;坐标数据.xlsx&#39;, # 文件位于 D:\桌面\温度数据\三维温度图\坐标数据.xlsx stl_file=&#39;complex_model.stl&#39;, # 文件位于 D:\桌面\温度数据\三维温度图\complex_model.stl xy_slice_z=300, # XY平面切片的Z坐标(mm) xz_slice_y=250, # XZ平面切片的Y坐标(mm) yz_slice_x=300 # YZ平面切片的X坐标(mm) ) # 运行分析 analyzer.run_analysis() 分析这个代码,解释每一步的含义,可以在哪里改动
10-16
### 函数参数传递与全局变量修改的关系 在 Python 中,函数内部修改参数值是否会影响全局变量,取决于变量的数据类型以及操作方式。 #### 不可变类型参数的修改不会影响全局变量 如果函数接收的是不可变类型(如整数、字符串、元组等),函数内部对该参数的修改不会影响外部的全局变量。例如: ```python x = 5 def modify_value(x): x = x * 2 modify_value(x) print(x) # 输出仍然是5 ``` 函数内部的 `x` 是局部变量,其值的改变不会影响全局变量 `x` 的值,因为不可变类型在赋值操作时会创建新的对象[^4]。 #### 可变类型参数的修改可能会影响全局变量 如果函数接收的是可变类型(如列表、字典等),并且在函数内部通过方法(如 `append`、`extend`、`+=` 等)修改其内容,则会影响全局变量的值。例如: ```python x = [1, 2, 3] def modify_list(x): x += [4, 5] modify_list(x) print(x) # 输出 [1, 2, 3, 4, 5] ``` 此处的 `x += [4, 5]` 实质上是调用了列表的 `extend` 方法,并未改变变量的引用,因此会直接影响全局变量 `x` 的内容[^1]。 若希望避免修改全局变量,可以在函数调用时传递其副本,而不是直接传递变量本身。例如: ```python modify_list(x[:]) # 传递列表的副本,不会影响全局变量 ``` #### 使用 `global` 关键字可直接修改全局变量 如果在函数内部希望直接修改全局变量的值,而不是通过参数传递,需要使用 `global` 关键字声明该变量。例如: ```python points = 4 def test(): global points points *= points test() print(points) # 输出16 ``` 若未使用 `global` 声明而直接尝试修改全局变量的值,Python 会认为该变量未在函数内部定义,从而抛出错误[^2]。 #### 函数内部未赋值操作不会影响全局变量 在某些情况下,函数内部虽然引用了全局变量,但没有对其进行赋值操作,全局变量的值不会受到影响。例如: ```python a = [1, 2, 3] def update_a(b): b.append(4) update_a(a) print(a) # 输出 [1, 2, 3, 4] ``` 由于 `append` 是列表的方法,修改的是对象本身的内容,而非创建新对象,因此全局变量 `a` 的值被修改了。但如果函数内部没有进行任何修改操作,则全局变量保持不变[^3]。 --- ### 总结 - 不可变类型参数在函数内部的修改不会影响全局变量。 - 可变类型参数在函数内部通过方法修改内容会影响全局变量;若不希望影响,应传递副本。 - 若希望在函数内部直接修改全局变量的值,必须使用 `global` 声明。 - 函数内部未对变量进行赋值操作时,全局变量的值不会被改变。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值