
1. 为什么lty参数值得单独拿出来讲R语言的基础绘图系统里lty这个参数看起来毫不起眼但它是我见过被问得最多的细节之一。新手画折线图时经常遇到一个尴尬场景两条线叠在一起颜色选了红和蓝打印出来变成灰度图后完全分不清谁是谁。这时候老手会告诉你把其中一条线的lty改成虚线就行了。问题解决了但新的问题来了——lty到底能填哪些值填数字和填字符串有什么区别为什么我填了lty3出来的效果和教程里不一样这篇文章就是要把lty这个参数彻底讲透。它属于R基础绘图系统base R graphics中控制线条类型的核心参数适用于plot()、lines()、abline()、segments()、rect()等几乎所有涉及线条绘制的函数。不管你是刚接触R的学生还是已经用惯了ggplot2但偶尔需要回到基础绘图的老用户把lty的取值规则、不同设备上的表现差异、以及和其他线型参数的配合方式搞清楚都能让你在出图时少走很多弯路。我自己的经验是lty的坑主要集中在三个地方一是数字和字符串的对应关系记混二是不同输出设备屏幕、PDF、PNG对线型的渲染不一致三是lwd线宽和lty配合时产生的视觉偏差。下面我会按照“先搞清取值体系再理解底层逻辑然后看实际效果最后解决常见问题”的顺序把这些问题一个一个拆开。2. lty参数的完整取值体系2.1 数字取值0到6的七种基本线型R的基础绘图系统中lty参数接受数字0到6每个数字对应一种预定义的线型。这个对应关系是固定的但不同资料里的描述方式不太一样我直接给一个最清晰的对照表数字值线型名称视觉效果描述适用场景0blank不画线完全透明隐藏某条线但保留图例位置1solid实线默认值主趋势线、主要数据系列2dashed短虚线次要数据系列、预测值3dotted点线辅助参考线、置信区间边界4dotdash点划线交替第三组数据、特殊标记线5longdash长虚线与短虚线区分时使用6twodash双短虚线第四组数据、密集区域区分这里有个细节值得注意lty0和ltyblank效果一样都是不画线。但很多人不知道的是当你设置lty0时lines()函数仍然会“绘制”这条线只是看不见而已。这意味着如果你后续要在这个位置添加图例图例中的线条也会是空白的。如果你只是想临时隐藏某条线用lty0没问题但如果你希望图例中显示线型但图中不画那就需要另想办法了。另一个容易混淆的点是数字0到6是索引值不是线型的“长度”或“密度”。比如lty2是短虚线lty5是长虚线但你不能通过lty2.5来得到一个“中等长度虚线”。R会直接报错或者忽略小数部分。我试过传lty2.5R没有报错但实际效果和lty2一样小数部分被截断了。所以老老实实用整数。2.2 字符串取值可读性更好的替代方案除了数字lty还接受字符串形式的取值。字符串和数字是一一对应的blank对应 0solid对应 1dashed对应 2dotted对应 3dotdash对应 4longdash对应 5twodash对应 6用字符串的好处是代码可读性更高。你写ltydashed半年后回来看代码一眼就知道这是虚线写lty2可能还得想一下2是啥。但字符串也有个坑不同版本的R或者不同的绘图设备对字符串的解析可能略有差异。我遇到过在某个Linux环境下ltylongdash渲染出来和ltydashed几乎一样的情况后来查了一下是设备驱动对长虚线的定义不同。所以如果你要跨平台出图建议用数字或者至少在目标设备上先测试一下。还有一个冷知识lty参数其实可以接受自定义的十六进制字符串来定义线型模式。比如lty44表示“画4个单位、空4个单位”循环lty13表示“画1个单位、空3个单位”。这个用法在R的文档里提到过但用的人不多。它的原理是字符串中的每个数字代表一段实线的长度相邻数字之间自动插入空白。比如44就是实线4、空白4、实线4、空白4……123就是实线1、空白2、实线3、空白1、空白2、实线3……等等这里要小心123的解析方式是实线1、空白2、实线3然后循环。所以实际模式是“1实2空3实2空1实2空3实……”。这个机制在需要非常精细的线型区分时很有用比如你要在一张图里放6条线基本线型不够用了就可以用自定义模式。2.3 数字与字符串的混用规则R允许在同一个绘图函数中混用数字和字符串吗答案是在向量化参数中R会尝试统一处理。比如你写ltyc(1, dashed, 3)R会把整个向量强制转换为字符型然后1变成1dashed保持3变成3。但1和dashed在内部解析时走的是不同分支所以最终效果是第一条线实线第二条线虚线第三条线点线。看起来没问题但如果你写ltyc(1, 2, 3)R会保持数字型效果一样。混用不会报错但为了代码清晰建议统一用一种类型。我个人的习惯是在快速探索性绘图时用数字因为打字快在最终出图脚本里用字符串因为可读性好。这个习惯供你参考。3. lty背后的底层逻辑它是怎么画出来的3.1 线型模式的单位是什么理解lty的关键在于搞清楚“一个单位”到底有多长。在R的基础绘图系统中线型模式dash pattern的长度单位是线宽lwd的倍数。也就是说如果你设置lwd2那么ltydashed中的“短虚线”每一段的长度也会相应放大。这个设计是有道理的线越粗如果虚线的段长不变看起来就会像一串密集的点失去虚线的视觉效果。所以R自动把线型模式与线宽绑定保证在不同线宽下线型都能保持可辨识度。但这也带来一个问题当你把lwd设得很大比如lwd5时ltydotted的点线会变成一个个很粗的短棒看起来像虚线而不是点线。这时候你可能需要换用ltydashed或者自定义更短的模式。我试过在lwd4的情况下用lty3出来的效果更像是一串小方块而不是点。后来改成lty11实线1、空白1才勉强像点线。所以记住线宽越大越要选择段长更短的线型模式。3.2 不同绘图设备对lty的渲染差异这是lty最让人头疼的地方。同样的代码在RStudio的绘图窗口、PDF输出、PNG输出、以及PostScript输出中线型可能看起来不一样。原因在于不同设备对线型模式的支持程度不同。屏幕设备如windows()、x11()、quartz()通常支持所有7种基本线型渲染效果比较标准。PDF设备pdf()也支持得不错但长虚线lty5在PDF中可能会被渲染成和短虚线差不多的样子因为PDF的线型定义方式不同。PNG设备png()依赖于底层的图形库在Linux和Windows上可能表现不一致。PostScript设备postscript()对线型的支持最完整但文件体积会大一些。我的经验是如果你要出PDF图尽量用lty1到lty4避免lty5和lty6因为这两个在PDF中辨识度不高。如果必须用6种线型考虑用自定义模式比如lty73、lty55等这样在不同设备上的一致性更好。还有一个坑当你把图输出为高分辨率PNG时如果lwd设得太小比如lwd0.5虚线可能会因为抗锯齿而变得模糊看起来像实线。这时候要么加大lwd要么换用点线。我一般建议在PNG输出时lwd至少设为1.5这样线型清晰可辨。3.3 lty与lwd、col的交互影响lty不是孤立起作用的它和lwd线宽、col颜色一起决定了线条的最终视觉效果。这三个参数的交互有几个值得注意的点第一lwd影响lty的段长这个前面说过了。第二col影响线型的可辨识度。浅色线条的虚线比深色线条的虚线更难辨认因为浅色降低了对比度。如果你用浅灰色画虚线建议把lwd调大一点或者改用点线。第三当多条线重叠时lty的区分效果取决于线宽。细线用不同lty区分效果很好但粗线lwd3用不同lty反而可能因为段长太大而看起来像断断续续的实线。我做过一个测试在同一张图上画4条线分别用lty1,2,3,4lwd1时区分度很好lwd3时lty2和lty4开始变得相似lwd5时只有lty1和lty3还能明显区分。所以如果你要用多种线型控制线宽在1到2之间是最稳妥的。4. 实操用lty画出可辨识的多线图4.1 基础示例4条线的折线图先来看一个最基础的例子用lty区分4条数据系列# 构造示例数据 x - 1:10 y1 - cumsum(rnorm(10)) y2 - cumsum(rnorm(10)) y3 - cumsum(rnorm(10)) y4 - cumsum(rnorm(10)) # 绘图 plot(x, y1, type l, lty 1, lwd 2, ylim range(c(y1, y2, y3, y4)), xlab 时间, ylab 数值, main 四种线型对比) lines(x, y2, lty 2, lwd 2) lines(x, y3, lty 3, lwd 2) lines(x, y4, lty 4, lwd 2) # 添加图例 legend(topright, legend c(系列A, 系列B, 系列C, 系列D), lty c(1, 2, 3, 4), lwd 2)这段代码里lty分别用了1、2、3、4对应实线、短虚线、点线、点划线。lwd2让线条足够粗线型清晰可见。图例中的lty参数和绘图中的保持一致这样读者能准确对应。这里有个细节legend()函数中的lty参数如果和plot()中的不一致图例会误导读者。我见过有人绘图用lty2图例却写了lty1结果图例显示实线但图中是虚线。这种错误在代码审查时很难发现因为图例和线条是分开设置的。建议把线型定义成变量绘图和图例都引用同一个变量line_types - c(1, 2, 3, 4) plot(x, y1, type l, lty line_types[1], lwd 2, ...) lines(x, y2, lty line_types[2], lwd 2) # ... legend(topright, legend c(A, B, C, D), lty line_types, lwd 2)这样改线型时只需要改一个地方不容易出错。4.2 进阶自定义线型模式当基本线型不够用时可以用自定义模式。比如你要画6条线基本线型只有7种包括blank但其中lty0不能用实际只有6种可用。如果6种还不够或者你想让某条线有独特的节奏就可以用自定义模式。自定义模式的写法是ltyXY其中X和Y是数字表示实线长度和空白长度。比如# 自定义线型示例 plot(1:10, type n, xlab , ylab , main 自定义线型) lines(1:10, rep(8, 10), lty 11, lwd 2) # 点线效果 lines(1:10, rep(7, 10), lty 22, lwd 2) # 短虚线 lines(1:10, rep(6, 10), lty 44, lwd 2) # 中虚线 lines(1:10, rep(5, 10), lty 88, lwd 2) # 长虚线 lines(1:10, rep(4, 10), lty 13, lwd 2) # 点划线 lines(1:10, rep(3, 10), lty 31, lwd 2) # 划点线 lines(1:10, rep(2, 10), lty 123, lwd 2) # 复杂模式这里lty11的效果和lty3点线类似但段长更短看起来更像点。lty123的模式是实线1、空白2、实线3、空白1、实线2、空白3……循环。这种复杂模式在需要高度区分时很有用但要注意不要用太多否则图例会变得难以阅读。自定义模式的一个限制是它只接受数字字符不能有字母或符号。lty1a会报错。另外模式字符串的长度没有硬性限制但太长的模式在渲染时可能会被截断。我试过lty123456789在PDF中渲染正常但在PNG中最后几段被忽略了。所以建议模式字符串不超过6个字符。4.3 参数选择的计算过程假设你要画一张图里面有5条线线宽统一为lwd2输出为PDF。你需要选择5种线型使得它们在PDF中都能清晰区分。怎么选第一步排除lty0blank因为你要画线。剩下1到6共6种。第二步考虑PDF对lty5和lty6的渲染可能不理想所以优先用1到4。第三步如果1到4只有4种不够5条线那就需要引入自定义模式。第四步自定义模式的选择要遵循“段长与线宽匹配”原则lwd2时段长在2到4之间比较合适。所以可以选lty22、lty33、lty44等。最终方案可以是lty1、lty2、lty3、lty4、lty33。这样5种线型在PDF中都能区分。如果你觉得lty33和lty2太像可以改成lty55但要注意PDF对长虚线的渲染。我实测下来lty55在PDF中比lty5更清晰因为自定义模式的段长是明确的不依赖设备的默认定义。还有一个计算细节当lwd变化时自定义模式的段长也会按比例缩放。所以如果你在屏幕上用lwd1调试然后输出PDF时改成lwd2线型的视觉效果会变化。建议在最终输出设备上直接调试线型不要在屏幕上调好了再换设备。5. 常见问题与排查技巧实录5.1 为什么我的lty设置没生效这是最常见的问题。你写了lty2但画出来的线还是实线。可能的原因有第一你设置的lty被后面的参数覆盖了。比如plot(x, y, typel, lty2, lty1)后面的lty1会覆盖前面的。R不会报错只会用最后一个值。检查代码中是否有重复的lty参数。第二你用的绘图函数不支持lty。比如barplot()虽然可以画边框但边框的线型需要用border参数配合而不是直接传lty。hist()的边框线型也是通过border参数控制的。如果你不确定某个函数是否支持lty查一下文档的“Graphical Parameters”部分。第三你设置的lty值超出了范围。比如lty7R会忽略这个值使用默认的lty1。ltyNA也会导致默认值。检查你的lty值是否在0到6之间或者是合法的字符串。第四你用的输出设备不支持该线型。比如某些老旧的图形设备可能只支持实线和虚线其他线型会被渲染成实线。换一个设备试试比如从png()换成pdf()。第五你的线条被其他图形元素遮挡了。比如你画了一条虚线但上面又画了一条实线虚线被盖住了。检查绘图顺序确保需要显示的线条最后画。5.2 不同设备下线型不一致怎么办这个问题没有完美的解决方案但有几个缓解策略策略一统一使用数字值避免字符串。数字值在不同设备上的解析更一致。策略二避免使用lty5和lty6这两个在不同设备上差异最大。如果必须用6种线型用自定义模式替代。策略三在最终输出设备上预览。不要只在RStudio的绘图窗口里看要实际生成PDF或PNG打开文件检查线型效果。策略四如果线型实在无法区分考虑用颜色或点形状pch来辅助区分。lty不是唯一的区分手段组合使用效果更好。我自己的做法是对于需要印刷或正式发布的图只用lty1、lty2、lty3三种配合颜色和点形状足够区分6到8条线。对于屏幕展示的图可以用到lty4和自定义模式。这个策略牺牲了一些线型多样性但换来了跨设备的一致性。5.3 lty与图例不匹配的排查图例中的线型和图中的线型不一致通常是因为图例的lty参数没有和绘图同步。排查步骤第一步检查图例的lty向量长度是否和legend标签长度一致。如果标签有4个lty向量只有3个R会循环使用导致错位。第二步检查图例的lty值是否和绘图中的值一一对应。建议把线型定义成命名向量绘图和图例都从这个向量取值。第三步如果图中用了自定义线型图例也要用相同的自定义字符串。比如图中lty33图例也要写lty33不能写lty2。第四步检查图例的lwd是否和图中一致。如果图中lwd2图例lwd1线型的视觉效果会不同可能看起来不匹配。下面是一个排查速查表问题现象可能原因解决方法线型全是实线lty值被覆盖或超出范围检查重复参数确认lty在0-6之间线型在不同设备上不同设备对线型支持差异统一用数字值避免lty5和6图例线型和图中不一致图例lty参数未同步用变量存储线型绘图和图例共用虚线看起来像实线lwd太小或分辨率太低增大lwd提高输出分辨率点线看起来像虚线lwd太大导致段长放大减小lwd或改用自定义短模式自定义线型不生效字符串格式错误确认只含数字长度不超过65.4 独家避坑技巧技巧一在脚本开头定义线型常量。比如LT_SOLID - 1、LT_DASHED - 2后面都用常量。这样改线型时只需要改一处而且代码可读性好。技巧二用par()设置全局线型。如果你整张图都用同一种线型可以在par()中设置lty这样所有绘图函数都会继承这个值。但要注意par()的设置会影响后续所有图记得在绘图结束后重置。技巧三测试线型时用abline()快速预览。abline(h1:6, lty1:6, lwd2)可以在一张图上快速展示6种线型的效果比反复修改plot()参数快得多。技巧四对于PDF输出用pdf()的useDingbats参数。这个参数默认是TRUE会使用Dingbats字体来渲染点线效果更清晰。如果点线看起来奇怪试试设为FALSE。技巧五在R Markdown中输出时注意fig.dev参数。不同的fig.dev如png、pdf、svg对线型的渲染不同。如果发现线型不对检查chunk选项中的dev设置。6. 和其他绘图系统的对比6.1 base R与ggplot2的线型设置差异ggplot2的线型设置和base R完全不同。ggplot2用linetype参数取值也是0到6但对应关系和base R不一样。比如ggplot2中linetype1是实线linetype2是虚线linetype3是点线这个和base R一致。但ggplot2的linetype4是点划线linetype5是长虚线linetype6是双虚线这个也和base R一致。所以基本对应关系是相同的。但ggplot2的线型渲染依赖于底层图形设备和base R一样存在设备差异。另外ggplot2的linetype可以用字符串比如solid、dashed、dotted等这个和base R也类似。最大的区别是ggplot2的线型模式不能自定义。你只能用预定义的几种不能像base R那样写lty33。所以如果你需要高度自定义的线型base R是更好的选择。6.2 lattice包的线型设置lattice包的线型设置用lty参数取值和base R一样。但lattice的默认主题可能会覆盖你的设置所以有时候你设了lty2但出来的还是实线。这时候需要检查trellis.par.get()中的plot.line设置或者用par.settings参数显式指定线型。lattice的一个优势是它自动处理多面板图的线型循环。如果你有多个面板每个面板有多条线lattice会自动分配不同的线型不需要手动设置。这个在base R中需要自己写循环。6.3 选择建议如果你需要精细控制线型尤其是自定义模式用base R。如果你需要快速出图且线型需求不复杂用ggplot2。如果你需要多面板图且希望自动处理线型用lattice。我个人的工作流是探索性分析用ggplot2因为快最终出图用base R因为控制精细。这个组合用了好几年效率很高。7. 实际项目中的线型策略7.1 学术论文图表的线型选择学术论文通常要求图在黑白打印下也能区分。这意味着不能依赖颜色必须用线型和点形状来区分。我的策略是2条线实线 虚线3条线实线 虚线 点线4条线实线 虚线 点线 点划线5条线实线 虚线 点线 点划线 自定义长虚线6条线实线 虚线 点线 点划线 自定义长虚线 自定义双虚线超过6条线时考虑分成多个子图或者用点形状辅助区分。学术论文中线型太多反而会让读者困惑所以尽量控制在4种以内。7.2 商业报告图表的线型选择商业报告通常用彩色输出线型的区分压力小一些。但为了投影效果线型仍然重要。我的策略是主数据系列用实线线宽稍大次要数据系列用虚线或点线参考线用点线颜色浅一些预测线用长虚线表示不确定性商业报告中线型的选择要服务于信息层级。重要的线用实线次要的线用虚线辅助的线用点线。这样读者一眼就能看出主次关系。7.3 线型与颜色的配合原则线型和颜色是互补的。如果颜色已经能区分线型可以统一用实线如果颜色区分度不够比如黑白打印线型就要承担区分任务。我的原则是颜色区分度高时线型统一用实线保持图面整洁颜色区分度低时线型多样化但不超过4种颜色和线型都多样化时注意不要超过6种组合否则图例会混乱浅色线条用实线深色线条可以用虚线或点线这个原则在大多数场景下都适用。你可以根据实际效果微调。8. 一些容易被忽略的细节8.1 lty在abline中的特殊行为abline()函数是画参考线的常用工具。它的lty参数和plot()一样但有一个特殊行为abline()的lty参数可以接受向量但只会使用第一个值。比如abline(hc(1,2,3), ltyc(1,2,3))三条水平线都会用lty1而不是分别用1、2、3。如果你想让三条线用不同线型需要分三次调用abline()。这个行为在R的文档中没有明确说明我是在实际使用中发现的。当时我想画三条不同线型的参考线结果全是实线查了半天才发现是abline()的限制。后来改成abline(h 1, lty 1) abline(h 2, lty 2) abline(h 3, lty 3)这样就正常了。8.2 lty在segments和arrows中的表现segments()和arrows()函数也支持lty参数但它们的lty是向量化的可以为每条线段指定不同的线型。比如segments(x0 c(1, 2, 3), y0 c(1, 2, 3), x1 c(4, 5, 6), y1 c(4, 5, 6), lty c(1, 2, 3))这样三条线段分别用实线、虚线、点线。这个特性在画网络图或流程图时很有用。但要注意arrows()的箭头部分不受lty影响箭头始终是实线。如果你想让箭头也变成虚线需要单独设置lty但实际效果可能不理想。我试过arrows(lty2)箭头的杆是虚线但箭头头还是实线看起来有点奇怪。所以如果要用虚线箭头建议用segments()画杆用points()画箭头头。8.3 lty在rect和polygon中的边界线型rect()和polygon()函数可以画矩形和多边形它们的边界线型用lty参数控制。但这两个函数的lty只影响边界不影响填充。如果你设置lty0边界会消失但填充还在。这个特性在画热力图或区域图时很有用。我遇到过一个问题用rect()画多个矩形每个矩形的边界线型不同但rect()的lty是向量化的可以为每个矩形指定不同的线型。但如果你同时设置了borderNAlty会被忽略。所以如果你想隐藏边界但保留填充用borderNA如果你想显示边界但用特定线型用lty和border配合。8.4 lty在par()中的全局设置par(lty2)可以设置全局线型之后所有绘图函数都会用这个线型除非在函数中显式覆盖。这个特性在批量出图时很有用但也很危险如果你忘了重置后续所有图都会用虚线。我的做法是在脚本开头保存原始参数op - par(lty1)在脚本结尾恢复par(op)。这样即使中间改了全局线型最后也能恢复。如果你在R Markdown中出图每个chunk都是独立的全局设置不会跨chunk生效所以不用担心。9. 线型调试的实用工作流9.1 快速预览所有线型我经常用下面这段代码快速预览所有线型的效果# 快速预览线型 op - par(mar c(2, 2, 2, 1)) plot(1:10, type n, xlab , ylab , main 线型预览, ylim c(0, 8)) for (i in 1:6) { lines(1:10, rep(i, 10), lty i, lwd 2) text(1, i, paste(lty , i), pos 4, cex 0.8) } par(op)这段代码会在一张图上画出6种线型每种线型旁边标注对应的数字。我通常在确定最终线型之前跑一遍看看在当前设备上哪种线型最清晰。9.2 在不同设备上测试确定线型后我会在目标设备上测试。比如如果最终输出是PDF我会pdf(test_lty.pdf, width 6, height 4) # 绘图代码 dev.off()然后打开PDF文件检查线型。如果发现某种线型不清晰就换一种重新生成PDF。这个过程可能需要重复两三次但能确保最终效果。9.3 线型与图例的联合调试图例是线型调试的最后一步。我通常先把图例加上然后检查图例中的线型是否和图中一致。如果不一致调整图例的lty参数。有时候图例中的线型看起来和图中不一样是因为图例的线宽和图中的线宽不同。这时候需要把图例的lwd也设成和图中一样。下面是一个联合调试的示例# 定义线型 lt - c(1, 2, 3, 4) lw - 2 # 绘图 plot(1:10, type n, ylim c(0, 5)) for (i in 1:4) { lines(1:10, rep(i, 10), lty lt[i], lwd lw) } # 图例 legend(topright, legend paste(系列, 1:4), lty lt, lwd lw, cex 0.8)这样图例和图中的线型、线宽完全一致不会出现不匹配的问题。10. 最后分享几个实战心得第一个心得线型的选择要考虑输出媒介。屏幕展示的图可以用细线和小段长印刷的图要用粗线和大段长。我一般屏幕用lwd1.5印刷用lwd2.5。第二个心得不要在一张图里用超过4种线型。超过4种读者很难记住哪种线型对应哪个系列。如果确实需要区分更多系列用颜色或分图。第三个心得线型的调试要趁早。不要等到图都画完了才调线型那时候改起来很麻烦。我通常在画第一版图时就确定线型后面只微调。第四个心得保存一份线型配置。我有个line_types.R文件里面定义了常用的线型组合比如lt_2lines - c(1, 2)、lt_3lines - c(1, 2, 3)等。每次画图时source()一下直接用这些配置省时省力。第五个心得如果线型实在调不好考虑用点形状pch辅助。有时候线型区分度不够但加上不同的点形状后区分度立刻提升。这个技巧在多线图中特别有用。线型这个参数说小也小说大也大。小到只是一个参数大到影响整张图的可读性。花点时间把它搞清楚后面画图会顺畅很多。