新闻详情

新闻详情

首页 / 资讯中心 / 详情

R语言VGAM包实战:Tobit模型处理受限因变量回归

发布时间:2026/9/24 23:53:00来源:尧图网络
R语言VGAM包实战:Tobit模型处理受限因变量回归
去年有个实际项目让我把tobit模型彻底摸清楚了分析影响家庭健康食品消费的因素调查数据里有将近三成家庭的月支出是0。同事一开始想直接跑多元线性回归我说你把那些0值当成普通数字处理回归线会被整体拉偏把0删掉呢又等于扔掉三成样本的信息。当时我用的就是R里的VGAM包一行vglm()搞定拟合也就是这个标题“VGAM | tobit模型”所指的东西。这篇博客我会从一个完整的模拟案例讲起手把手演示从数据构造、模型拟合、系数解读到边际效应计算的完整流程同时对比AER、censReg、survival这几个包做出来的结果有什么区别最后整理我在实际使用中踩过的坑。无论你是做经济实证、市场研究还是社会调查只要因变量里有大量“囤积在边界”的数值这篇文章应该都能省你不少试错时间。1. tobit模型到底在解决什么问题1.1 受限因变量和“一堆0”的数据先看一类很常见的场景家庭健康食品月支出、个人捐赠金额、某App的登录时长、公司研发投入……这些变量有个共同点——大量观测值恰好落在0这个点上剩下的观测值是连续正数。数据长这个样子summary(health_data$expend) # Min. 1st Qu. Median Mean 3rd Qu. Max. # 0.000 0.000 1.842 3.512 5.130 18.2100在这类数据里往往有两种含义一种是“这个月确实没消费”另一种是“这个月没买但不代表永远不会买”。从统计建模角度我们真正关心的是那个看不见的、连续的“消费意愿”潜变量而观测到的0只是意愿低于某个门槛后的表现。这种数据结构在计量经济学里有一个专门的名字被审查数据censored data也叫受限因变量limited dependent variable。如果因变量的取值范围在某个点上被“切断”而且有大量观测值堆在切断点上那么你需要的就是tobit模型。Tobit这个名称来源于经济学家James Tobin1958年他研究家庭耐用消费品支出时发现直接用OLS拟合这种数据会得到有偏的估计于是提出了这个“截断回归”方案。后来人们把这个经典的0截断版本称为Type I Tobit。1.2 一个潜变量框架看懂模型结构Tobit模型的核心思路是引入一个不可直接观测的潜变量latent variable( y^* )它服从经典的线性模型[ y^*_i x_i^T \beta \varepsilon_i,\quad \varepsilon_i \sim N(0, \sigma^2) ]然后通过一个观测规则把潜变量映射到我们能看到的( y_i )上[ y_i \begin{cases} y^_i, \text{if } y^_i 0 \ 0, \text{if } y^*_i \le 0 \end{cases} ]用人话说就是潜变量是“真实的消费意愿”它可以取负值、也可以取正值但我们只能看到它落在正数范围内的实际值一旦意愿低于0这个阈值观测值就被压成了0。这样做的妙处在于模型既利用了大量0值样本提供的信息“意愿低于门槛”又保留了正数样本的连续数值信息比简单扔掉0值或者给0值强行赋一个人为小数都更合理。所以tobit模型本质上是在估计两个东西一个是回归系数( \beta )另一个是误差项的标准差( \sigma )。( \sigma )不是一个可有可无的冗余参数它直接影响似然函数中概率的计算也影响边际效应的大小后面第3节会专门讲。在该模型的似然函数推导中正数观测的贡献是常规正态密度( f(y_i|x_i) )而0值观测的贡献是累积概率( P(y^*_i \le 0) \Phi(-x_i^T\beta/\sigma) )。把所有样本的对数似然加起来然后通过最大似然估计找最优的( \beta )和( \sigma )这就是tobit模型的标准估计流程。1.3 为什么不能直接跑OLS也不能简单删0先用直觉理解OLS在截断数据上为什么会出问题。线性回归的基本假设是误差项( \varepsilon )在任何( x )取值下都保持对称分布、均值为0。但当你把数据在0处截断后那些本应取负值的( y^* )全部变成了0整体分布被“拦腰切断”误差项在低位部分不再对称均值也不再为0。你用OLS拟合这样的数据得到的截距项会被往上拉斜率项也会往0的方向收缩也就是所谓“估计向0衰减”attenuation bias。更极端的情况是如果( x )本身与0值比例相关比如低收入家庭更容易出现0支出那么OLS不仅系数有偏连符号方向都可能出问题。相比之下tobit通过似然函数显式地把“0的概率”和“正值的分布”同时纳入模型相当于告诉优化器这些0是截断的结果而不是真的等于0。至于“把0值删掉只跑正值部分”的做法看起来省事实则更糟。它丢弃了所有0值样本的信息——而这些0往往集中在某个特定群体比如低收入人群里导致样本选择偏差。实践中你会发现只用正数部分拟合出来的系数会比tobit偏小而且标准误会偏大本质上是样本量变小加选择性丢失的双重损失。所以简单总结当你的因变量有大量边界值而且边界值反映了“未达到门槛”的真实潜在状态时tobit模型就是默认的正解。2. VGAM包实操从模拟数据到模型拟合2.1 为什么选VGAM而不是其他包在做tobit模型的R实现时市面上有好几个方案AER包的tobit()、censReg包的censReg()、survival包的survreg()以及VGAM包的vglm()。我选择把VGAM作为主要讲解对象原因有三一是VGAMVector Generalized Additive Models提供了统一的框架可以覆盖从经典回归到广义可加模型的几乎所有情况学好它的vglm()就能顺带处理很多其他模型二是tobit()作为VGAM的family函数参数设计直观后续可以扩展成带平滑项的广义可加tobit模型三是它在模型比较、假设检验、交叉验证等配套工具上做得比较完整适合在真实项目中扎根使用。当然“适合”不等于“唯一”第4节我会对比其他包的差异方便你根据使用场景选择。2.2 构建一份模拟数据为了让你能完整复现整个流程我构造一份模拟数据假设我们要分析家庭健康食品支出单位千元/月受哪些因素影响自变量包括家庭月收入千元、户主年龄、受教育年限和家庭人口数。数据生成过程中设定真实的截距和系数这样后面可以核验模型估计是否接近真实值。set.seed(1234) n - 800 income - rlnorm(n, meanlog 3.5, sdlog 0.5) age - rnorm(n, mean 40, sd 12) age - pmax(pmin(age, 70), 20) edu - sample(12:22, n, replace TRUE) family_size - sample(1:6, n, replace TRUE) xb - -8 2.5 * log(income) 0.08 * edu 0.01 * age - 0.3 * family_size ystar - rnorm(n, mean xb, sd 2.5) expend - pmax(0, ystar) health_data - data.frame(expend expend, income income, age age, edu edu, family_size family_size) mean(expend 0) # 大约在0.27左右意味着约三成的0值比较符合常见业务场景这里有几个设定值得说明收入取对数后纳入模型更符合经济学中“收入边际效应递减”的直觉误差标准差设为2.5是为了让0值比例控制在一个常见水平20%30%。如果标准差设得太小0值会非常少模型退化成接近普通回归如果设得太大0值比例可能超过一半这时要考虑hurdle模型或Zero-Inflated模型了第5节会展开。2.3 用vglm函数一行代码拟合tobit模型接下来就是核心步骤library(VGAM) m_tobit - vglm(expend ~ log(income) edu age family_size, family tobit(Lower 0, Upper Inf), data health_data) summary(m_tobit)tobit(Lower 0, Upper Inf)指定了因变量的下审查点为0上审查点为无穷大这就是标准左截断tobit。如果你遇到的是双边截断比如考试分数0到100分都有堆积可以把它改成tobit(Lower 0, Upper 100)。summary输出中比较关键的部分是这样Coefficients: Estimate Std. Error z value Pr(|z|) (Intercept):1 -7.12836 1.03171 -6.909 4.87e-12 *** log(income) 2.60177 0.21895 11.883 2e-16 *** edu 0.07949 0.02335 3.405 0.000661 *** age 0.01062 0.00643 1.651 0.098693 . family_size -0.28138 0.04595 -6.123 9.18e-10 *** (Intercept):2 0.91647 0.02724 33.639 2e-16 ***先别急着解读系数先注意一个新手极易看懵的细节输出里有两个截距分别为(Intercept):1和(Intercept):2。第一个是线性预测子即潜变量均值的截距第二个对应的是对数尺度上的标准差参数也就是( \log(\sigma) )。要还原误差标准差需要做指数变换log_sigma - coef(m_tobit)[(Intercept):2] sigma - exp(log_sigma) sigma # 2.5左右我们模拟数据时设定的( \sigma2.5 )exp(0.91647)约等于2.50说明模型还原度很高。这个“第二个截距需要exp还原”的坑我见过不少人栽在上面——直接拿(Intercept):2当成标准差去算边际效应结果整整差了一个数量级。至于回归系数的解读log(income)的系数2.60表示在其他条件不变的情况下收入每提高1%潜变量消费意愿平均增加0.026单位edu系数0.079表示每多受一年教育意愿平均提高0.079family_size系数-0.281则表示家庭人口每多一人消费意愿降低0.281。2.4 提取系数、预测与模型比较在实操中我们经常需要把回归系数单独提取出来做后续计算。注意coef()返回的是一个命名向量直接用向量索引会比较稳coefs - coef(m_tobit) beta - coefs[1:5] # 依次为截距、log(income)、edu、age、family_size names(beta)如果你需要参数估计的协方差矩阵比如做Wald检验可以用vcov(m_tobit)。VGAM也提供了lrtest()可以对嵌套模型做似然比检验例如检验是否该保留某个变量m_tobit_no_age - vglm(expend ~ log(income) edu family_size, family tobit(Lower 0, Upper Inf), data health_data) VGAM::lrtest(m_tobit, m_tobit_no_age)这里age的P值约0.10边缘显著。如果样本数据再大一点年龄效应可能就会进入显著区留还是不留需要结合业务判断。预测值的处理值得单独提醒一句predict(m_tobit, type response)返回的对象在很多版本里是一列潜变量均值而不是审查后的( E(y|x) )。如果你的目的要得到“考虑0值影响后的期望支出”需要用第3节的公式手动计算。最稳妥的办法是不要偷懒自己用系数算一遍后面我会给完整代码。3. 系数不是终点三类边际效应与可视化3.1 为什么不能直接拿系数当边际效应很多人在做完tobit回归后直接说“income的系数是2.6所以收入每增加1%支出增加0.026单位”。严格来讲这句话只对潜变量( y^* )成立对观测值( y )并不成立。因为0值通过概率机制参与了期望值的计算自变量变化时不仅潜变量均值会变“落入0值区域的概率”也会变所以观测支出( E(y|x) )的边际效应并不等于( \beta )。实际业务中我们通常关心三种不同的效应对潜变量( y^* )的边际效应就是回归系数( \beta_k )本身。对无条件期望( E(y|x) )的边际效应它考虑的是全部样本含0方向与( \beta_k )相同但幅度要打个折扣( \beta_k \times \Phi(x^T\beta/\sigma) )。对正值条件期望( E(y|y0,x) )的边际效应它只针对那些“已经进入正数区间”的样本公式为( \beta_k \times [1 - \lambda(z)(z \lambda(z))] )其中( z x^T\beta/\sigma )( \lambda(z)\phi(z)/\Phi(z) )是逆米尔斯比率。这里的直觉是如果数据中0值比例很高那么Φ和1 - λ(z)(zλ(z))都会明显小于1直接用( \beta )做业务解读会严重高估自变量对实际支出的影响。反过来如果0值几乎没有这些修正因子约等于1tobit模型也就退化成普通回归了。3.2 实战计算三类边际效应下面这段代码从拟合好的m_tobit出发选取一组代表性样本点比如中位数样本计算三类边际效应coefs - coef(m_tobit) beta - coefs[1:5] # 截距 4个变量系数 sigma - exp(coefs[(Intercept):2]) # 取一组代表性样本收入中位数、教育16年、年龄42、家庭3人 new_data - data.frame(income median(health_data$income), edu 16, age 42, family_size 3) X_new - model.matrix(~ log(income) edu age family_size, data new_data) xb - as.numeric(X_new %*% beta) z - xb / sigma # 逆米尔斯比率 lambda - dnorm(z) / pnorm(z) # 三类边际效应以 log(income) 为例也可换成其他变量 me_latent - beta[log(income)] me_uncond - beta[log(income)] * pnorm(z) me_cond - beta[log(income)] * (1 - lambda * (z lambda)) data.frame( 效应类型 c(潜变量E(y*), 无条件E(y), 正值条件E(y|y0)), 边际效应 c(me_latent, me_uncond, me_cond) )在模拟数据下pnorm(z)大约在0.710.73之间1 - λ(z)(zλ(z))则更小一些。这意味着用beta[log(income)]2.60直接去解释实际支出会把你对收入效应的判断放大30%甚至更多。所以汇报结果时一定要说明你算的是哪一类边际效应这在学术论文和商业分析里都是常见审稿/挑战问题。3.3 可视化收入与支出的非线性关系tobit模型另外一个直观的好处是你可以画出“收入变化如何影响预测支出”的非线性曲线。思路是固定其他变量让收入在一定范围内变化用模型系数算出潜变量均值( x^T\beta )然后分别计算( E(y) )和( E(y|y0) )income_seq - seq(5, 150, length.out 200) newdata_seq - data.frame(income income_seq, edu 16, age 42, family_size 3) X_seq - model.matrix(~ log(income) edu age family_size, data newdata_seq) xb_seq - as.numeric(X_seq %*% beta) z_seq - xb_seq / sigma Ey - pnorm(z_seq) * xb_seq sigma * dnorm(z_seq) Ey_pos - xb_seq sigma * dnorm(z_seq) / pnorm(z_seq) plot(income_seq, Ey, type l, ylim c(0, max(Ey_pos) * 1.1), xlab 家庭月收入千元, ylab 健康食品支出预测值, main tobit模型下收入对支出的预测) lines(income_seq, Ey_pos, lty 2) lines(income_seq, pmax(0, xb_seq), lty 3) legend(topleft, bty n, legend c(E(y), E(y|y0), 潜变量均值), lty c(1, 2, 3))这条曲线能清楚看到三个层次潜变量均值是直线( E(y|y0) )在低收入段会明显高于潜变量均值因为观测到正支出的家庭本身已经“过滤”掉了低意愿样本( E(y) )在低收入段被压得很低因为那部分样本大量堆积在0上。这种分层可视化对业务汇报特别有用比你直接扔一个回归系数表格有说服力得多。4. 换工具AER、censReg、survreg与手写MLE对比4.1 各方案横向对比VGAM并不是唯一选择。我实际用下来各个包的定位差异很明显实现方案包/函数优点需要注意的点VGAMvglm(familytobit())框架统一适合复杂扩展配套检验工具齐全输出里第二个截距是log(sigma)容易看错AERtobit(left0, rightInf)计量经济学经典语法最直观适合标准tobit无法直接处理面板/随机效应等扩展censRegcensReg(left0, rightInf)直接输出sigma支持面板随机效应生态相对小调试信息较少survivalsurvreg(distgaussian)支持多种分布和生存分析框架互通需要构造Surv对象新手容易搞混事件/截断标记手写MLEoptim/maxLik完全透明可随意扩展各类变体需要自己写似然函数和初值调试成本高从效率角度日常标准tobit分析我推荐AER的tobit()简洁明了但如果要做好扩展、做模型比较、或者未来要升级成广义可加tobitVGAM会更有优势。下面分别看看其它几个包的调用方式以及输出如何对齐。4.2 AER与censReg的快速实现library(AER) m_aer - tobit(expend ~ log(income) edu age family_size, left 0, right Inf, data health_data) summary(m_aer)AER的输出会多一行Log(scale)实际上是( \log(\sigma) )记得exp还原。censReg的优点是直接输出sigma不用做变换library(censReg) m_cens - censReg(expend ~ log(income) edu age family_size, left 0, right Inf, data health_data) summary(m_cens)跑完你会发现三个包在相同数据集上给出的( \beta )和( \sigma )基本上一致差异只在小数点后第三四位可以放心使用。如果你在真实数据上发现某个包的结果明显异常优先检查数据和收敛问题而不是怀疑包本身。4.3 survreg的特殊语法survreg写起来稍绕但理解了左截断数据在生存分析框架里的表示方式就不难了library(survival) m_surv - survreg(Surv(expend, expend 0, type left) ~ log(income) edu age family_size, data health_data, dist gaussian) summary(m_surv)这里的Surv(expend, expend 0, type left)意思是expend是观测值expend 0作为是否“事件发生”的指示变量。对左截断数据来说event1表示观测到了精确值event0表示观测值被截断在0。survreg输出的Scale参数就是( \sigma )不再是log尺度。4.4 手写MLE把tobit内核彻底看穿如果你想彻底理解tobit或者想改造成自己的变体手动写最大似然估计是最锻炼人的方式。完整代码如下tobit_ll - function(par, X, y) { beta - par[1:ncol(X)] log_sd - par[ncol(X) 1] sd - exp(log_sd) xb - as.numeric(X %*% beta) ll - ifelse( y 0, dnorm(y, mean xb, sd sd, log TRUE), pnorm(0, mean xb, sd sd, log.p TRUE) ) -sum(ll) } X - model.matrix(~ log(income) edu age family_size, data health_data) # 初值用OLS系数和ln(sd(y))一般收敛很快 init - c(coef(lm(expend ~ log(income) edu age family_size, data health_data)), log(sd(health_data$expend))) fit_mle - optim(init, tobit_ll, X X, y health_data$expend, method BFGS, hessian TRUE) fit_mle$par # 标准误 sqrt(diag(solve(fit_mle$hessian)))手工实现的优势和代价都在代码里你能清楚看到似然函数由“正数部分的密度”和“0值部分的累积概率”两部分拼接而成代价是当你添加更多限制比如参数约束、稳健标准误时会多不少工作量。用VGAM这种现成包就没有这些烦恼但有时间的话手写一遍对理解输出里每个数字都极其有帮助。5. 常见问题与排查技巧实录5.1 模型不收敛怎么办用vglm()拟合tobit时偶尔会碰到不收敛或出现“iteration limit exceeded”的警告。最常见的原因有两个一是自变量量纲差异太大比如收入是以元为单位、年龄是两位数量级差了好几个数量级导致优化过程不稳定二是初值离真实值太远。解决办法很简单先对连续型自变量做标准化减去均值除以标准差跑完模型再把系数换算回原始尺度或者参照OLS的估计值手动传入初值。如果你用的是optim手写MLE初值建议直接用coef(lm(...))并保证似然函数中dnorm和pnorm的输入参数没有NaN。检查一下输入X里有没有缺失值也是基本功。5.2 0值比例太多或太少怎么办tobit模型默认的假设是0值来自同一个数据生成过程的截断。如果业务逻辑告诉你0值背后是完全不同的决策机制比如“从不购买”的人和“这个月恰好没买”的人是两拨人那么tobit模型就不够用了。前者可以考虑hurdle模型两阶段先建模“是否购买”再建模“购买多少”后者可以考虑Zero-Inflated Tobit或样本选择模型Heckman两步法。总体经验是如果0值比例超过50%先别急着上tobit想清楚你的业务场景属于哪一类。反过来如果0值比例只有不到5%模型估计会很脆弱因为数据里几乎没有信息来识别( \sigma )的尾部概率导致边际效应的修正项极不稳定。这种时候老老实实用OLS或加个box-cox变换可能更实际。5.3 异方差问题怎么检查tobit模型依赖误差项同方差的正态假设。一旦实际数据存在异方差估计的系数和标准误都会偏离。实践中我习惯在建模前先做个简单的分组描述把X按高低分组看看正数部分的支出分布是否方差接近。也可以直接比较tobit残差在不同拟合值区间上的波动程度。如果发现异方差明显有几个备选路径一是对因变量做对数变换但要注意对数变换后0值需要单独处理比如log(1y)二是用VGAM里更灵活的框架给( \sigma )也加一个线性预测子例如拟合vglm(expend ~ ..., family tobit(...), ...)时考虑让scale参数随协变量变化。当然这种扩展模型解释起来会麻烦不少建议只在问题确实严重时使用。5.4 不同包输出对不上是怎么回事经常有朋友跑来问为什么VGAM里第二个截距是0.9AER里却输出Log(scale)0.9而censReg直接给sigma2.5到底谁是对的答案是三个都对只是参数化不同。VGAM和AER输出的是( \log(\sigma) )censReg输出的是( \sigma )survreg输出的Scale也是( \sigma )。换算关系就一个公式( \sigma e^{\log\sigma} )。另一个对齐要点是系数的顺序和命名。VGAM把两个线性预测子的截距放在一起默认第1个是均值截距、第2个是log(sigma)AER则把Log(scale)放在系数表最后。如果你写脚本去coef()提取记得看名字不要用脆弱的数字索引。5.5 报告结果时的常见小坑最后说一个容易被审稿人/业务方挑战的点一定要在报告里说明你汇报的是哪一类边际效应。很多人拿着beta[log(income)]2.60就说“收入显著影响支出”但如果业务关心的是实际观测支出那么边际效应应该是2.60乘以0.72左右也就是大约1.87。不区分清楚项目评审会上很容易被问出破绽。我个人在实际操作中的一个习惯是先在脑海里跑一遍数据生成过程——我是模拟数据时就知道真实参数而在真实数据上我会先做table(y 0)、summary(y[y0])、分组均值透视这些基本功确认0值比例和正数分布都合理再上tobit。模型跑完后再用第3节的代码手动算一次边际效应和包输出的结果互相验证。这套流程虽然朴素但在极多项目里帮我避开了潜在的解释错误。如果你也想把这个方法用到自己的数据里强烈建议先从模拟数据开始跑通再换到真实数据——这样你能清楚知道哪些差异来自抽样误差哪些是模型设定带来的意外。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

BOSS直聘数据分析师岗位爬虫与薪资预测模型实战 2026/9/25 1:59:53

BOSS直聘数据分析师岗位爬虫与薪资预测模型实战

简介:这是一套面向计算机与数据科学专业学生的学术实践项目,聚焦招聘平台数据分析岗位的信息挖掘与智能研究,可用于课程设计、毕业项目筹备及实战技能提升。项目以BOSS直聘数据分析师职位为对象,完整覆盖网络爬虫构建、多维统计解…

阅读更多 →
公交站牌广告系统技术选型:Java MVC MySQL实战解析 2026/9/25 1:59:53

公交站牌广告系统技术选型:Java MVC MySQL实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
BP神经网络自适应PI控制永磁同步电机 2026/9/25 1:59:53

BP神经网络自适应PI控制永磁同步电机

简介:本资源是一份面向电机控制工程师与自动化专业学生的永磁同步电机(PMSM)智能控制技术实践资料,聚焦BP神经网络动态整定PI参数这一核心难点,解决传统PID在负载突变、参数漂移等工况下响应迟滞、超调大、鲁棒性差的问…

阅读更多 →
PanabitFREE源码开发环境搭建与DPI模块编译指南 2026/9/25 1:59:53

PanabitFREE源码开发环境搭建与DPI模块编译指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
HTML5多图片上传预览核心实现与内存优化:从FileReader到ObjectURL 2026/9/25 1:59:47

HTML5多图片上传预览核心实现与内存优化:从FileReader到ObjectURL

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
双相交错PFC设计实战:UCC28070与HP1013选型对比及调试技巧 2026/9/25 1:59:47

双相交错PFC设计实战:UCC28070与HP1013选型对比及调试技巧

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞