R语言网络分析与路径规划——线数据应用实战:规划散步路线

一、 打破"最短路径"的迷思

在日常生活中,我们习惯了依赖商业地图软件提供的导航服务。这些软件背后的算法高度封装,通常只遵循一个铁律:效率至上。它们计算路径的核心逻辑是将距离或时间作为唯一的成本,试图用最短的时间把你从 A 点送到 B 点。然而,这种计算方式往往较为单一,无法涵盖更多的应用场景。这时候,商业软件往往束手无策。而这正是 R 语言大显身手的地方------它是一个强大的地理空间分析平台,能够在地理数据的基础上,赋予我们打破常规、重新定义路线规则的能力。

R 语言在处理地理数据方面拥有独特的优势,不同于普通的绘图软件只能展示静态的地图图片,R 能够深入理解地图背后的数据信息。我们将利用 R 语言,将 2015 Street Tree Census - Blockface Data 这一数据集作为示例,完成数据清洗、坐标投影转换,到构建空间网络图这些任务,最终实现基于自定义权重的路径搜索。以此深入了解 R 语言在地理数据集方面的应用价值,并探索其高级应用方法。

二、 Blockface 数据的清洗与投影转换

我们首先打开RStudio:

图1 找到RStudio并打开

将附件的数据集名字重命名为2015_Street_Tree_Census_Blockface.csv,然后将其粘贴到R的工作目录内。由于目前我们并不知道工作目录在哪,因此需要现在R studio中输入代码进行寻找:

R 复制代码
getwd()

输入后,便能够得到返回结果,找到当前的工作目录:

图2 返回的工作目录

将获得的地址粘贴到此电脑的地址栏中,回车进入:

图3 粘贴到地址栏回车进入目录

将示例数据集改名并粘贴到这一目录:

图4 改名并粘贴

在开始阶段,我们并不能马上开始规划路线。原始的地理数据包含了大量对于规划散步路线来说冗余的信息,且其坐标系统可能并不适合直接进行距离计算。在 R 语言中,我们主要依赖 sf 扩展包来处理这类工作。sf 包的强大之处在于,它将复杂的地图数据降维成了我们熟悉的表格形式,每一行代表一条街道,每一列代表一个属性,而特殊的 geometry 列则存储了街道的形状信息。这使得我们可以像处理 Excel 表格一样,轻松地对地图数据进行筛选和清洗。

首先,我们需要将数据读入 R 环境,并检查数据情况。复制并运行以下代码:

R 复制代码
install.packages(c("sf", "dplyr"))



# 加载必要的空间分析与数据处理包

library(sf)

library(dplyr)



# 1. 读取 GeoJSON 数据

# st_read 函数会自动识别文件格式并将其转换为 sf 对象

raw_data <- st_read("2015_Street_Tree_Census_Blockface.geojson", quiet = TRUE)



# 查看所有列名

names(raw_data)



# 查看前几行数据的概览

head(raw_data)

可以看到,结果已经输出了相关信息:

图5 数据结果

观察结果能够发现,当前加载的数据集不包含树木数量的列,而是包含物理街区几何数据与种植季节规划数据。这份数据提供了街道的形状和长度,但没有包含"这条街上有几棵树"的普查统计结果。我们可以利用 R 语言强大的数据处理能力,基于现有的数据模拟一个树木数量。根据街道的长度来生成树木数量:假设街道越长,树可能越多,并加入一些随机性。复制并粘贴运行以下代码:

R 复制代码
library(sf)

library(dplyr)



# 1. 读取数据

raw_data <- st_read("2015_Street_Tree_Census_Blockface.geojson", quiet = TRUE)



# 2. 模拟数据与清洗

set.seed(123)



blockface_simulated <- raw_data %>%

  mutate(

    # 先将 segmentlen 强制转换为数字类型

    # 这样计算机就能进行数学运算了

    segmentlen_num = as.numeric(segmentlen),

    

    # 使用转换后的 segmentlen_num 进行计算

    simulated_count = (segmentlen_num / 50) + runif(n(), min = -5, max = 5),

    

    # 取整并去除负数

    tree_count = as.integer(pmax(0, simulated_count))

  )



# 3. 筛选与投影

blockface_clean <- blockface_simulated %>%

  select(tree_count, geometry) %>%

  mutate(tree_count = ifelse(is.na(tree_count), 0, tree_count))



# 转换为 EPSG:2263 (New York Long Island)

blockface_projected <- st_transform(blockface_clean, crs = 2263)



# 4. 检查结果

print("数据处理成功!前几行数据如下:")

print(head(blockface_projected))



# 绘图检查

plot(blockface_projected["tree_count"],

     main = "街道网络与模拟绿荫分布",

     lwd = 0.5)

运行代码后可以发现,已经成功输出了模拟的分布结果:

图6 模拟的分布结果图

通过这段代码,我们成功地将一份复杂的原始地理文件,转化为了一个干净、单位统一且适合数学计算的 R 对象,为接下来构建精确的空间网络奠定了坚实的基础。

三、 构建空间网络:从 sf 对象到 sfnetwork 图结构

经过清洗与投影,我们手中的 blockface_projected 对象虽然看起来已经很像一张地图了,但在计算机的眼里,它依然只是一堆毫无关联的线条集合。我们需要将这些独立的街道线条编织成一张互联互通的网。在数据科学中,这种结构被称为图,它由节点和边组成。

在 R 语言的生态系统中,sfnetworks 是一个革命性的扩展包。它巧妙地结合了处理空间几何的 sf 包和处理网络图论的 tidygraph 包。通过它,我们可以轻松地将地理空间数据转化为网络图结构。在这个过程中,sfnetworks 会自动识别每一条街道的起点和终点,将它们视为节点,并将街道本身视为连接这些节点的边。这样一来,原本孤立的线条就变成了具有拓扑关系的网络:计算机终于知道,当你走到某条街的尽头时,可以无缝地转向另一条街。

当我们把 sf 对象转换为 sfnetwork 对象时,原始数据中的属性(比如之前保留的 tree_count)会被自动附着在"边"上。这意味着网络中的每一条连接线不仅知道自己有多长,还知道自己上面长了多少棵树。这种带属性的边正是我们要实现导航规划的核心基础。复制并粘贴运行以下代码:

R 复制代码
install.packages(c("sfnetworks", "tidygraph"))



library(sfnetworks)

library(tidygraph)

library(sf)

library(dplyr)



# 1. 检查当前的几何类型(这一步是为了验证问题,你会看到 MULTILINESTRING)

print("转换前的几何类型:")

print(unique(st_geometry_type(blockface_projected)))



# 2. 使用 st_cast 将 MULTILINESTRING 拆解为 LINESTRING

# 这一步会将那些由多段线组成的记录拆分成独立的行,属性(如 tree_count)会自动复制

blockface_lines <- st_cast(blockface_projected, "LINESTRING")



# 再次检查几何类型(现在应该是 LINESTRING 了)

print("转换后的几何类型:")

print(unique(st_geometry_type(blockface_lines)))



# --- 修正步骤结束 ---





# 3. 现在可以放心地构建网络了

street_net <- as_sfnetwork(blockface_lines, directed = FALSE)



# 4. 激活"边"数据并计算长度

street_net <- street_net %>%

  activate("edges") %>%

  mutate(edge_length = st_length(geometry))



# 5. 查看网络结构信息

print(street_net)



# 6. 可视化验证

# 由于网络可能很大,我们只画一部分或者简化绘制

plot(st_geometry(street_net, "edges"), col = "gray", lwd = 0.5, main = "构建完成的街道网络")

完成运行后的结果如下图所示:

图7 完成运行后的结果

运行这段代码后,可以会发现 street_net 对象中多出了关于节点和边的统计信息。每一条街道都有了它的物理长度(edge_length)和生态属性(tree_count),这张网已经做好了被算法遍历的准备。不过,在正式开始导航之前,我们还需要解决现实数据中常见的一个棘手问题------网络中的断头路和孤岛。

四、 拓扑修复:处理网络断点与提取最大连通子图

在理想的数学模型中,所有的街道都是完美连接的。但在现实世界的地理数据里,情况往往没那么乐观。由于数字化过程中的微小误差,两条本该相交的街道可能在坐标上相差了几厘米,导致计算机认为它们处于断开状态;或者某些偏僻的社区道路可能完全独立于主干道网络之外,形成一个个孤岛。如果直接在这样的网络上规划路线,算法可能会报错,或者规划出一条根本无法到达终点的路径。因此,我们需要针对路径进行拓扑修复。

首先要解决伪断点问题。例如一个十字路口,四条街道本应汇聚于一点,但因为绘图误差,它们可能只是无限接近而没有真正重合。这会导致网络中出现大量不必要的节点,甚至造成路网中断。虽然 sfnetworks 在构建时会自动融合部分节点,但更稳妥的做法是提取网络中的最大连通子图。最大连通子图是这张网络中互相连通的那一部分,而且是包含节点数量最多的那一部分。在城市路网中,这通常对应着城市的主体交通网络。通过提取最大连通子图,我们可以自动剔除那些漂浮在主网之外的细碎孤岛(比如公园里封闭的环形跑道,或者数据错误的悬空路段),确保无论我们选择哪个起点和终点,它们之间都一定存在一条可达的路径。这不仅能提高算法的稳定性,还能大幅减少计算量。

利用 tidygraph 提供的图算法工具,我们可以轻松地给网络中的每个群组打上标签,然后筛选出成员最多的那个群组。复制并粘贴运行以下代码:

R 复制代码
# 加载必要的包

library(sfnetworks)

library(tidygraph)

library(dplyr)

# 加载 igraph 包以使用 vcount 等底层图论函数

library(igraph)



# 1. 分析网络的连通性

# group_components() 会给网络中每一个独立的连通区域分配一个唯一的 ID

# 我们可以先看看网络被分成了多少个互不相连的"孤岛"

street_net_components <- street_net %>%

  activate("nodes") %>%

  mutate(component_id = group_components())



# 打印各连通子图的节点数量情况

# 你可能会发现有一个巨大的群组包含绝大多数节点,而其他群组只有寥寥几个节点

# as_tibble() 将结果转为表格以便查看

print(table(street_net_components %>% pull(component_id)))



# 2. 提取最大连通子图

# 我们的目标是只保留那个最大的网络,丢弃那些细碎的孤岛

street_net_connected <- street_net %>%

  activate("nodes") %>%

  mutate(component_id = group_components()) %>%

  # 按照 component_id 分组,计算每个组的大小

  group_by(component_id) %>%

  mutate(component_size = n()) %>%

  ungroup() %>%

  # 筛选条件:只保留节点数最多的那个组

  filter(component_size == max(component_size))



# 3. 清理与简化

# 提取完子图后,可能会残留一些不再使用的属性,我们可以整理一下

# 并再次激活 'edges' 模式,准备下一步的权重计算

final_street_net <- street_net_connected %>%

  activate("edges")



# 4. 验证结果

# 使用 igraph::vcount() 获取节点数量

cat("原始网络节点数:", vcount(street_net), "\n")

cat("最大连通子图节点数:", vcount(final_street_net), "\n")



# 可视化修复后的主干网络

# 注意:如果网络依然很大,绘图可能需要几秒钟

plot(st_geometry(final_street_net, "edges"),

     col = "darkgreen", lwd = 0.5,

     main = "拓扑修复后的最大连通街道网络")

输出结果会包含相应的内容:

图8 获得的节点数与修复后的网络结果

经过这一步处理,final_street_net 变成了一个干净、紧密互联的网络对象。所有的死路和孤岛都被剥离,剩下的就是城市交通的坚实骨架。现在我们可以放心地在这个骨架上定义绿荫规则,开始规划那条散步路线了。

五、 定义阻抗函数:如何量化街道的"绿荫成本"

在传统的导航算法中,路径规划的核心依据是"阻抗",通常直接等同于距离或时间,即走过 100 米的路,成本就是 100。但在本次案例中,这个逻辑被颠覆了。我们认为走过一条光秃秃的 100 米街道,不仅身体疲惫,心情也烦躁,其实际心理成本可能高达 200;而走过一条绿树成荫的 100 米街道,微风拂面,心情舒畅,其实际心理成本可能只有 50。为了让计算机理解这种人类的感受,我们需要设计一个自定义阻抗函数,目标是创造一个新的权重指标,暂且称之为 green_cost(绿荫成本)。这个指标应该遵循以下逻辑:

1.基础成本是长度:无论风景多美,路毕竟是要一步步走的,所以物理长度仍然是基础。

2.树木是减速带:这里的减速是指减少阻力。树越多,阻力应该越小。

3.树木密度的概念:单纯看树木总数是不够的。一条 10 米长的路有 5 棵树,和一条 1000 米长的路有 5 棵树,绿化体验完全不同。因此,我们需要引入树木密度的概念,即每米长度上的树木数量。

基于上述逻辑,我们可以设计一个简单的公式:

或者更简单直接一点,给每棵树设定一个折扣系数。但在实际操作中,为了避免出现负数成本,我们通常采用加权法:

其中,权重系数与树木密度成反比。如果某条路树很多,权重系数就接近 0.5(相当于距离打五折);如果没树,权重系数就是 1(原价);甚至可以惩罚无树路段,设为 1.5(加价)。

在 R 语言中,我们可以利用 mutate 函数轻松地将这个逻辑应用到网络的每一条边上。我们将计算每条街道的树木密度,并据此生成一个新的权重列 green_weight。复制并粘贴运行以下代码:

R 复制代码
# 1. 计算树木密度与定义绿荫权重

final_street_net <- final_street_net %>%

  activate("edges") %>%

  mutate(

    # 强制将 tree_count 转换为数值型

    # 即使它已经是数字,这一步也是安全的;如果是字符,它会变成数字

    tree_count = as.numeric(tree_count),

    

    # 处理 length_val,剥离单位

    length_val = as.numeric(edge_length),

    

    # 再次检查是否有 NA 产生(防止转换过程中出现非数字字符变成 NA)

    tree_count = ifelse(is.na(tree_count), 0, tree_count),

    length_val = ifelse(is.na(length_val), 0.1, length_val), # 防止长度为NA,给个极小值

    

    # 计算树木密度:每英尺有多少棵树

    tree_density = tree_count / (length_val + 0.001),

    

    # --- 定义核心阻抗函数 ---

    # 逻辑:树越多,权重越小(路越"好走")

    # 公式:weight = 1 / (1 + 因子 * 密度)

    weight_factor = 1 / (1 + 50 * tree_density),

    

    # 最终计算"绿荫成本"

    green_cost = length_val * weight_factor

  )



# 2. 查看计算结果

edge_preview <- final_street_net %>%

  as_tibble() %>%

  select(tree_count, length_val, tree_density, weight_factor, green_cost) %>%

  arrange(desc(tree_density))



print("街道绿荫成本预览(前6行):")

print(head(edge_preview))



# 3. 检查数据类型

print(class(final_street_net %>% pull(tree_count)))

print(class(final_street_net %>% pull(length_val)))

运行后,可以看到前六行的成本计算预览结果:

图9 获得的预览结果

通过这段代码,我们实际上完成了一次价值重估。在 green_cost 中,一条 500 米长的林荫大道,可能比一条 300 米长的暴晒小路还要短。计算机并不懂风景,但它懂数字,现在我们已经成功地把风景变成了数字,接下来我们就可以利用算法,开始规划路线。

六、 路径规划实战:基于自定义权重的最短路径算法

现在,我们的网络图 final_street_net 已经拥有了两套截然不同的成本体系:一套是物理距离,另一套是绿荫成本。接下来,我们将在地图上随机选取两个点------起点和终点,并分别命令计算机找出这两点之间的"最优解"。

在 R 语言的 sfnetworks 和 tidygraph 框架下,计算最短路径通常使用经典的 Dijkstra 算法 或 A 算法*。你不需要理解这些算法背后的复杂数学推导,只需要知道它们的作用是:在一个网络中,找到连接两个节点且总权重最小的那条线。我们将进行两次路径搜索:

1.传统模式:权重设为 edge_length。这就是普通地图软件给你的路线,追求物理上的最近。

2.绿荫模式:权重设为 green_cost。这是我们定制的路线,算法会为了降低"绿荫成本"而自动选择绕行那些树木茂密的街道,哪怕物理距离会远一点。

我们随机从网络中抽取两个节点作为起点和终点,复制并粘贴运行以下代码:

R 复制代码
# 加载路径规划所需的包

library(sfnetworks)

library(tidygraph)

library(sf)



# 1. 确定起点和终点

# 为了演示,我们从网络中随机抽取两个节点 ID

# set.seed 确保每次运行结果一致,方便复现

set.seed(1234)

net_nodes <- st_as_sf(final_street_net, "nodes")

sample_indices <- sample(nrow(net_nodes), 2)



start_node_index <- sample_indices[1]

end_node_index <- sample_indices[2]



cat("起点索引:", start_node_index, " | 终点索引:", end_node_index, "\n")



# 2. 计算【最短路径】 (Shortest Path)

# 使用 st_network_paths 函数

# weights 参数指定为物理长度 edge_length

path_shortest <- st_network_paths(

  final_street_net,

  from = start_node_index,

  to = end_node_index,

  weights = "edge_length"

)



# 3. 计算【最绿路径】 (Greenest Path)

# 同样的函数,唯一的区别是 weights 参数改为我们自定义的 green_cost

# 算法会自动倾向于选择 green_cost 小的路段(即树多的路)

path_greenest <- st_network_paths(

  final_street_net,

  from = start_node_index,

  to = end_node_index,

  weights = "green_cost"

)



# 4. 提取路径几何形状

# st_network_paths 返回的是节点的索引,我们需要把它转换回具体的街道线条(geometry)

# 这是一个辅助函数,用于从网络中提取路径的几何信息

get_path_geometry <- function(net, path_indices) {

  # 提取路径经过的节点索引

  node_indices <- unlist(path_indices$node_paths)

  # 利用 slice 提取这些节点,然后提取它们之间的边

  # 注意:这里简化处理,直接提取节点连线可能不够精确,

  # 更严谨的做法是提取 edge_paths

  edge_indices <- unlist(path_indices$edge_paths)

  

  net %>%

    activate("edges") %>%

    slice(edge_indices) %>%

    st_as_sf()

}



# 获取两条路径的实际形状

geometry_shortest <- get_path_geometry(final_street_net, path_shortest)

geometry_greenest <- get_path_geometry(final_street_net, path_greenest)



# 5. 计算结果对比

# 让我们看看为了"绿",我们多走了多少路

length_shortest <- sum(geometry_shortest$edge_length)

length_greenest <- sum(geometry_greenest$edge_length)



cat("=== 路线对比 ===\n")

cat("匆忙的最短路线长度:", round(length_shortest, 2), "英尺\n")

cat("惬意的最绿路线长度:", round(length_greenest, 2), "英尺\n")

cat("为了风景多走的距离:", round(length_greenest - length_shortest, 2), "英尺 (+",

    round((length_greenest - length_shortest)/length_shortest * 100, 1), "%)\n")



# 6. 简单绘图预览

# 绘制底图(灰色背景路网)

plot(st_geometry(final_street_net, "edges"), col = "gray90", lwd = 0.5, main = "路线对比")



# 绘制最短路径(红色)

# 使用 st_geometry 确保只绘制线条,避免属性列干扰

plot(st_geometry(geometry_shortest), col = "red", lwd = 2, add = TRUE)



# 绘制最绿路径(深绿色)

plot(st_geometry(geometry_greenest), col = "darkgreen", lwd = 2, add = TRUE)



# 绘制起点和终点(蓝色圆点)

# 使用 st_geometry() 包裹节点对象

# 这样 R 就只处理点的坐标,不会去查属性表,从而避免报错

start_end_points <- net_nodes[c(start_node_index, end_node_index), ]

plot(st_geometry(start_end_points), col = "blue", pch = 19, cex = 1.5, add = TRUE)



# 添加图例

legend("topright", legend = c("最短路线", "最绿路线", "起终点"),

       col = c("red", "darkgreen", "blue"), lwd = c(2, 2, NA), pch = c(NA, NA, 19))

运行结果后可以发现,路线已经完成了计算,并被标记在了处理过后的网络图之上:

图10 完成计算后的预览图

七、 可视化对比:匆忙的"最短路线" 惬意的"最绿路线"

作为地理空间分析,没有什么比一张清晰的对比地图更有说服力了。我们需要验证:那条所谓的"最绿路线",是否真的避开了光秃秃的水泥路,穿过了树木茂密的街区?在这一部分,我们将不再满足于基础的 plot 绘图,而是引入 R 语言中专业的专题地图制作包 tmap。它采用图层叠加的语法逻辑,允许我们将街道背景、树木密度分布、以及两条截然不同的规划路线叠加在一起,制作出一张既美观又具分析价值的地图。我们将以树木密度作为背景底色,直观地展示城市绿肺的分布,并将两条路线高亮显示,从而一目了然地看到算法的决策逻辑。

复制并粘贴运行以下代码:

R 复制代码
library(ggplot2)

library(sf)

library(dplyr)



# 1. 数据优化:引入"相对排名"

# 我们不直接画绝对密度,而是把所有街道按密度排队,分成 10 个等级 (Deciles)

# 这样保证了地图上一定有 10% 的最深绿,和 10% 的最浅灰

background_edges_viz <- st_as_sf(final_street_net, "edges") %>%

  mutate(

    # ntile 函数将数据分为 10 份,1 代表密度最低的 10%,10 代表密度最高的 10%

    density_rank = ntile(tree_density, 10)

  ) %>%

  arrange(density_rank) # 排序,确保深色的画在上面



# 2. 绘制分级可视化地图

viz_plot_final <- ggplot() +

  # --- 图层 1:背景路网 (基于排名着色) ---

  geom_sf(data = background_edges_viz,

          aes(color = as.factor(density_rank)), # 将排名转为因子,变成离散颜色

          size = 1.2,

          alpha = 1) +

  

  # 使用离散的渐变色板:从灰到绿

  scale_color_manual(

    values = colorRampPalette(c("gray90", "palegreen", "darkgreen"))(10),

    name = "绿荫等级 (1-10级)",

    guide = guide_legend(nrow = 1) # 图例横向排列

  ) +

  

  # --- 图层 2:最短路径(红色) ---

  geom_sf(data = geometry_shortest,

          color = "red",

          size = 1.5,

          alpha = 0.9) +

  

  # --- 图层 3:最绿路径(蓝色虚线) ---

  geom_sf(data = geometry_greenest,

          color = "blue",

          size = 1.5,

          linetype = "dashed",

          alpha = 0.9) +

  

  # --- 图层 4:起点和终点 ---

  geom_sf(data = start_end_points,

          color = "black",

          fill = "yellow",

          shape = 21,

          size = 5,

          stroke = 1.5) +

  

  # --- 图层 5:美化 ---

  labs(title = "路径规划对比:效率优先 vs 绿荫优先",

       subtitle = "背景已按密度排名分级:等级越高,树木相对越密",

       caption = "数据来源:2015 Street Tree Census") +

  theme_void() +

  theme(

    plot.title = element_text(hjust = 0.5, face = "bold", size = 16),

    plot.subtitle = element_text(hjust = 0.5, size = 12),

    legend.position = "bottom",

    legend.title = element_text(size = 10),

    legend.key.width = unit(1, "cm")

  )



# 3. 输出最终地图

print(viz_plot_final)

运行代码后,可以发现结果已经得到了输出:

图11 完成输出的结果

观察生成的地图,可以清晰地看到两条路线的差异逻辑:

1.红色路线(最短路径):红线径直连接了起点和终点。它毫不犹豫地穿过了地图中间颜色较浅(树木较少)的区域,因为它只在乎距离的缩短,不在乎头顶是否有烈日。

2.深蓝路线(最绿路径):这条虚线则显得更加蜿蜒。在某些路口,它明显偏离了直线方向,特意绕行到了旁边深绿色的街道上。虽然这让路线看起来稍微弯曲了一些,但它成功地将行人的大部分行程锁定在了高密度的绿荫之下。

通过这次实战,我们不仅学会了如何清洗地理数据、构建拓扑网络,更重要的是,我们掌握了利用 R 语言自定义成本的能力。这些技巧可以应用到地理数据的处理工作中,让 R 语言的作用得到充分的发挥。

相关推荐
LOnghas12113 小时前
文化遗产物品识别与分类——基于Mask R-CNN的改进模型详解
分类·r语言·cnn
斯摩尔德3 小时前
重测序及群体遗传分析
r语言
YangYang9YangYan3 小时前
2026大专大数据技术专业学习数据分析的必要性
大数据·学习·数据分析
拓云者也3 小时前
常用的生物信息学数据库以及处理工具
数据库·python·oracle·r语言·bash
-To be number.wan1 天前
Python数据分析:Matplotlib 绘图练习
python·数据分析·matplotlib
ASD123asfadxv1 天前
汽车保险丝盒元件识别与分类:基于Faster R-CNN与ResNet101的检测方案详解
分类·r语言·汽车
天桥下的卖艺者1 天前
使用R语言编写一个生成金字塔图形的函数
开发语言·数据库·r语言
BYSJMG1 天前
计算机毕设选题推荐:基于大数据的癌症数据分析与可视化系统
大数据·vue.js·python·数据挖掘·数据分析·课程设计
YIN_尹1 天前
【MySQL】数据分析双剑客:聚合函数 与 group by子句的完美搭配
mysql·性能优化·数据分析