使用ggplot绘制双轴图时遭遇secondary axes非单调转换错误求助
解决ggplot二级轴非单调转换错误
错误信息
Error in `f()`: ! transformation for secondary axes must be monotonic Run `rlang::last_error()` to see where the error occurred. Warning messages: 1: In (vsmow + 1000)/(d18osw) : longer object length is not a multiple of shorter object length 2: In log(al) : NaNs produced
问题代码
temp_function=function(x){ vsmow=((x*1.03086)+30.86) al=((vsmow+1000)/(d18osw)+1000) temp= ((2.559*(10^6))/(1000*log(al))-0.715) temp2=((sqrt(temp))-273.15) return(temp2) } d18o= c(1.57250607, 1.80213529, 2.04688684, 2.05768980, 1.98708459, 1.34778009, 1.00992742, 0.49403459, 0.56191185, 0.19579498, 0.08956884, -0.45381030, -0.60158300, -0.92339630, -1.24378840, -1.26242530, -1.08654800, -1.26757590, -1.55244220, -1.53874470, -1.11724190, -1.48195450, -1.42071900, -1.34024320, -1.02823520, -1.18611540, -0.70669460, -0.07921330, 0.62126673, 0.72444074, 0.87499759, 0.90721387, 1.24767007, 1.21508923, 1.03826488, 1.04047116, 1.18316613, 1.08727293, 1.20401308, 1.09286830, 1.06370239, 1.02918556, 1.02596777, 0.77820699, 0.80191575, 0.68288250, 0.41364236, 0.52444324, 0.42412122, 0.47631340, 0.18180808, 0.24770451, -0.13031250, -0.29202420, -0.63853890, -0.75578190, -0.97535620, -0.94015680, -1.32330740, -1.25992660, -1.42660920, -1.63518900, -1.49774430, -1.74429840, -2.01900870, -1.56466740, 0.16242522, 1.01083316, 1.40150034, 1.32526660, 1.30147320, 1.46371530, 1.28995767, 0.99914156, 1.01710591, 1.03839202, 0.98460735, 0.85265740, 0.66789528, 0.39425361, -0.32123960, -0.65520020, -1.19385120, -1.13205130, -1.12317560, -1.36345920, -1.56758210, -1.82638240, -1.89979770, -1.65500800, -1.62422980, -0.18052760, 1.01357724) d18osw=c(0.29183729, 0.27975000, 0.10502778, -0.06572483, 0.17118316, -0.19654514, -0.06997483, 0.32036111, -0.13143750, 0.42998436, 0.52024392, 0.56215365, 0.64008507, 0.41656163, 0.34580208, 0.52225087, 0.46624826, 0.31072483, 0.33935330, 0.26682292, 0.70016768, 0.40168663, -0.20191667, 0.41197222, 0.24525257, -0.02140972, 0.47027628, 0.89164670, 0.77396007, 0.56274392, 0.49325347, 0.33585665, 0.53992969, 0.16491146, 0.23636150, 0.34210183, 0.44513814, 0.46146701, 0.46509146, 0.48237162, 0.43156944, 0.09048896, 0.09671962, 0.04099018, 0.50198958, 0.31100521, 0.05997069, -0.15857716, 0.76692962, 0.73520833, 0.64020312, 0.61566233, 0.65118229, 0.77298611, 0.75675347, 0.55096351, 0.55380645, 0.56227947, 0.51668750, 0.53445486, 0.37260069, 0.34081424, 0.40993576, 0.15124653, 0.30723284, -0.03553212, 0.36715538, 0.50027778, 0.50542795, 0.49255990, 0.27760284, 0.08988271, 0.06961960, 0.48275673, 0.03034288, 0.53711111, 0.61154514, 0.65742448, 0.46065538, -0.51660851, 0.16469010, -0.30371007, 0.60969817, 0.08988271, -0.41425000, 0.10897251, 0.08988271, 0.08988271, 0.16909337, 0.19873437, 0.30460382, -0.04704174, -0.20544909) samnum=seq(1,93,1) df=as.data.frame(cbind(samnum,d18o)) colnames(df)=c("samnum","d18o") dfplot=ggplot(df, aes(x=samnum)) + theme_bw()+ theme(panel.border = element_blank(), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), axis.line = element_line(colour = "black"))+ geom_point( aes(y=d18o,x=samnum),size =2.5)+ scale_y_reverse(name = expression(paste(delta^'18','O (‰)')),breaks=c(-3,-1.5,0,1.5,3), sec.axis = sec_axis(~temp_function(.), name = "Temperature (°C)"))
问题原因
- 非单调转换:ggplot二级轴要求转换函数是纯函数(输入值唯一对应输出值,且输入单调变化时输出也单调递增/递减)。但你的
temp_function依赖全局向量d18osw,相同的d18o输入可能对应不同的温度输出,导致转换关系非单调。 - 向量长度不匹配:
d18o和d18osw长度一致,但函数内运算时未按逐元素绑定处理,触发R的循环补齐警告。 - NaN生成:
al可能出现<=0的情况,导致log(al)生成NaN,后续sqrt(temp)也会因temp为负报错。
正式解决方案
方案一:预计算温度,建立全局单调映射
先计算每个样本的温度,再拟合d18o与温度的单调模型,用模型作为二级轴的转换函数:
# 修正后的温度计算函数(处理NaN问题) temp_function_vec <- function(x, d18osw) { vsmow <- ((x * 1.03086) + 30.86) al <- ((vsmow + 1000)/(d18osw) + 1000) # 确保al>0,避免log报错 al <- pmax(al, 1e-6) temp <- ((2.559*(10^6))/(1000*log(al)) - 0.715) # 确保temp>0,避免sqrt报错 temp <- pmax(temp, 1e-6) temp2 <- ((sqrt(temp)) - 273.15) return(temp2) } # 构建包含温度列的数据框 df <- data.frame(samnum = samnum, d18o = d18o, temp = temp_function_vec(d18o, d18osw)) # 拟合d18o与温度的线性模型(保证单调) fit <- lm(temp ~ d18o, data = df) # 生成二级轴转换函数:d18o -> 温度 trans_func <- function(x) predict(fit, newdata = data.frame(d18o = x)) # 绘制图表 dfplot <- ggplot(df, aes(x = samnum)) + theme_bw() + theme(panel.border = element_blank(), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), axis.line = element_line(colour = "black")) + geom_point(aes(y = d18o), size = 2.5) + scale_y_reverse(name = expression(paste(delta^'18','O (‰)')), breaks = c(-3, -1.5, 0, 1.5, 3), sec.axis = sec_axis(trans_func, name = "Temperature (°C)", breaks = seq(10, 30, 5))) dfplot
方案二:单调插值映射(更贴合逐样本计算)
如果线性拟合不够准确,可基于排序后的d18o和温度生成单调插值函数:
# 预计算温度列(同方案一) df <- data.frame(samnum = samnum, d18o = d18o, temp = temp_function_vec(d18o, d18osw)) # 排序数据,生成单调插值函数 sorted_df <- df[order(df$d18o), ] mono_trans <- approxfun(sorted_df$d18o, sorted_df$temp, rule = 2) # 绘制图表 dfplot <- ggplot(df, aes(x = samnum)) + theme_bw() + theme(panel.border = element_blank(), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), axis.line = element_line(colour = "black")) + geom_point(aes(y = d18o), size = 2.5) + scale_y_reverse(name = expression(paste(delta^'18','O (‰)')), breaks = c(-3, -1.5, 0, 1.5, 3), sec.axis = sec_axis(mono_trans, name = "Temperature (°C)", breaks = seq(10, 30, 5))) dfplot
关键说明
- ggplot二级轴是视觉映射工具,不是数据关联工具,必须使用单调、输入输出一一对应的转换函数。
- 预计算温度后再建立全局映射,既满足逐样本计算的业务需求,又符合ggplot的设计逻辑。
- 修正后的函数通过
pmax处理了NaN生成问题,避免运算报错。
内容的提问来源于stack exchange,提问作者gbraniecki27
相关产品推荐
相关产品推荐

