0

0

一文助你全面理解合成控制方法

PHPz

PHPz

发布时间:2023-05-25 14:56:19

|

1343人浏览过

|

来源于51CTO.COM

转载

译者 | 朱先忠

审校 | 孙淑娟

现在人们普遍认为,对其感兴趣的结果(疾病、公司收入、客户满意度等)计算处理(药物、广告、产品等)因果效应的金标准技术是AB测试,也称为随机实验。我们将一组受试者(患者、用户、客户等)随机分为实验组和对照组,并对实验组进行实验。这一程序确保了在事前两测试组之间的唯一预期差异是由实验本身引起的。

☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

一文助你全面理解合成控制方法

AB测试的关键假设之一是实验组和对照组之间没有污染。给实验组的一名患者用药不会影响对照组患者的健康。例如,如果我们试图治愈一种传染病,而这两个群体并不是孤立的,情况可能就不是这样。在行业中,经常违反污染假设的是网络效应(我使用社交网络工具的次数随着网络朋友的增加而增加)和一般均衡效应(如果我改进一种产品,可能会减少另一种类似产品的销售)。

由于这个原因,实验通常是在足够大的规模下进行的,这样就不会造成跨群体(例如城市、州甚至国家)的污染。然后,由于规模更大,出现了另一个问题:治疗变得更昂贵。在医院给50%的患者用药比给一个国家50%的城市用药便宜得多。因此,通常仅治疗少数患者群体,但通常治疗时间较长。

在这样的需求环境下,大约10年前就出现了一种非常强大的方法:合成控制。合成控制的思想是:利用数据的时间变化而不是横截面变化(跨时间而不是跨单位)。当前,这种方法在业界(例如在谷歌、优步、Facebook、微软和亚马逊等公司)已经非常流行,因为它易于理解,而且能够应对经常是大规模出现的需求环境。在这篇文章中,我们将通过一个例子来探索这项技术:我们将调查自动驾驶汽车在共享乘车平台上的有效性。

自动驾驶汽车案例

假设您是一个共享乘车平台,您想测试车队中自动驾驶汽车的效果。

可以想象,对于这种类型的功能运行AB测试存在很多限制。首先,将单个游乐设施随机化很复杂。其次,这是一项非常昂贵的干预措施。第三,也是统计上最重要的一点,你不能在乘坐水平上进行这种干预。问题在于,从实验单元到对照单元都存在溢出效应:如果自动驾驶汽车确实更高效,这意味着它们可以在相同的时间内为更多的客户提供服务,减少了普通司机(对照组)可获得的客户。这种溢出污染了实验,并阻止了对结果的因果解释。

出于所有这些原因,我们只选择了一个城市。而且,鉴于这篇文章的合成氛围,我们选择了美国佛罗里达州第二大城市迈阿密!

一文助你全面理解合成控制方法

迈阿密天际线

在实验中,我生成了一个模拟数据集。我们将在其中观察一组美国城市随时间的变化。其中,收入数据是由数据组成的,而社会经济变量取自经合组织​​2022大都市区数据库​​​。我从​​文件src.dgp​​​中导入数据生成过程dgp_selfdriving。另外,还从​​文件src.utils​​中导入一些绘图函数和库。

from src.utils import *
from src.dgp import dgp_selfdriving

treatment_year = 2013
treated_city = 'Miami'
df = dgp_selfdriving().generate_data(year=treatment_year, city=treated_city)
df.head()

一文助你全面理解合成控制方法

数据快照

到目前为止,我们已经拥有自2002年至2019年期间美国46个最大城市的信息。注意到,面板中的数据是平衡的。这意味着,我们能够在所有时间段观察所有城市。而另一方面,自驾汽车是从2013年开始推出。

作为实验单元的迈阿密是否与样本的其余实验单元具有可比性?让我们使用优步公司开发的​​causalml软件包​​中的create_table_one函数来生成一个协变量平衡表,其中包含实验组和对照组的可观察特征的平均值。顾名思义,这应该是您在因果推理分析中呈现的第一个表。

from causalml.match import create_table_one

create_table_one(df, 'treated', ['density', 'employment', 'gdp', 'population', 'revenue'])

一文助你全面理解合成控制方法

平衡表

正如预期的那样,这两个测试组并不平衡:迈阿密的人口密度更高、更穷、更大,就业率也低于我们样本中的美国其他城市。

我们有兴趣了解自动驾驶汽车的引入对收入的影响。

一个最初的想法是,像我们在AB测试中那样分析数据,比较对照组和实验组。在引入自动驾驶汽车后,我们可以通过实验组和对照组之间收入均值的差异来估计实验效果。

smf.ols('revenue ~ treated', data=df[df['post']==True]).fit().summary().tables[1]

一文助你全面理解合成控制方法

回归结果

自动驾驶汽车的影响似乎是负面的,但并不显著。

这里的主要问题是实验不是随机分配的。我们创建一个单独的实验单元,迈阿密,它很难与其他城市相比。

另一种方法是,比较迈阿密实验前后的收入。

smf.ols('revenue ~ post', data=df[df['city']==treated_city]).fit().summary().tables[1]

一文助你全面理解合成控制方法

回归结果

自动驾驶汽车的影响似乎是正面的,在统计上也是显著的。

然而,这一程序的问题在于,2013年之后可能会发生许多其他事情。将所有差异归因于自动驾驶汽车是一个相当大的挑战。

如果我们绘制城市收入的时间趋势图,我们可以更好地理解这种担忧。首先,我们需要将数据重新格式化为宽格式,每个城市一列,每年一行。

df = df.pivot(index='year', columns='city', values='revenue').reset_index()

现在,让我们绘制迈阿密和其他城市的收入。

cities = [c for c in df.columns if c!='year']
df['Other Cities'] = df[[c for c in cities if c != treated_city]].mean(axis=1)
def plot_lines(df, line1, line2, year, hline=True):
sns.lineplot(x=df['year'], y=df[line1].values, label=line1)
sns.lineplot(x=df['year'], y=df[line2].values, label=line2)
plt.axvline(x=year, ls=":", color='C2', label='Self-Driving Cars', zorder=1)
plt.legend();
plt.title("Average revenue per day (in M$)");

既然我们在谈论迈阿密,让我们选择一个合适的调色板以便突出显示(粉紫色)。

sns.set_palette(sns.color_palette(['#f14db3', '#0dc3e2', '#443a84']))plot_lines(df, treated_city, 'Other Cities', treatment_year)

一文助你全面理解合成控制方法

正如我们所见,在迈阿密进行实验后,收入似乎有所增加。但这是一个非常不稳定的时间序列。该国其他地区的收入也在增加。从这个图表展示的结果很难将这种变化主要归因于自动驾驶汽车。

我们能做得更好吗?

合成控制

答案是肯定的!当我们只有一个实验单元和多个对照单元,并且随着时间的推移来观察它们时,合成控制允许我们进行因果推断。这个想法很简单:将未处理的单元组合在一起,使它们尽可能接近实验单元的行为,而不进行处理。然后使用这种“合成单元”作为控制。该方法最早由Abadie、Diamond和Hainmueller(2010)提出,被称为“过去几年政策评估文献中最重要的创新”。此外,由于其简单性和可解释性,在行业中得到了广泛应用。

实验背景

我们假设,对于一组独立同分布受试者,随着时间t=1,…,T,我们将观察到一组变量(Xᵢ,Dᵢ,Yᵢ),这包括:

  • 实验任务Dᵢ∈{0,1}(已处理)
  • 响应Yᵢₜ∈R(收入)
  • 特征向量Xᵢₜ∈Rⁿ(人口、密度、就业和GDP)

此外,在时间t*(在我们的案例中为2013年)处理一个单元(在我们的例子中为迈阿密)。我们区分实验前的时间段和实验后的时间段。

至关重要的是,实验D不是随机分配的。因此,实验单元和对照组之间的均值差异不是平均治疗效果的无偏估计值。

问题

问题在于,与往常一样,我们没有观察到实验单元的反事实结果,即,我们不知道如果他们没有接受实验,会发生什么。这被称为因果推理的基本问题。

最简单的方法就是比较实验前后的时间。这被称为事件研究方法。

然而,我们可以做得更好。事实上,即使实验不是随机分配的,我们仍然可以访问一些未接受实验的单元。

对于结果变量,我们观察到以下值:

一文助你全面理解合成控制方法

结果变量

在上述表达式中,Y⁽ᵈ⁾ₐ表示在给定实验任务a和实验状态d的时间t时刻的结果。我们基本上有一个数据缺失的问题,因为我们没有观察到Y⁽⁰⁾ₒ:如果不进行实验(d=0),那么实验单位(a=t)会发生什么情况。

解决方案

根据​​Doudchenko和Inbens(2018)​​的研究成果,我们可以将实验单元的反事实结果估计为对照单元观察结果的线性组合。

一文助你全面理解合成控制方法

合成控制结果

上面公式中:

  • 常数α允许两组之间的不同平均值
  • 权重βᵢ允许在控制单元i之间变化(否则,这将是差异中的差异)

我们应该如何选择使用哪些权重呢?我们希望我们的合成控制在实验前尽可能接近结果。第一种方法是将权重定义为:

一文助你全面理解合成控制方法

合成控制权重

也就是说,权重应使控制单元Xc的可观察特性与实验前的实验单元X之间的距离最小。

您可能会注意到,这与线性回归非常相似。事实上,我们正在做一些非常相似的事情。

在线性回归中,我们通常有许多单位(观测值),很少有外部特征和一个内生特征,我们试图将内生特征表示为每个单位内生特征的线性组合。

一文助你全面理解合成控制方法

线性回归

对于合成控制,我们有许多时间段(特征),很少的控制单元和单个实验单元,我们尝试将实验单元表示为每个时间段控制单元的线性组合。

为了执行相同的操作,我们基本上都需要对数据进行转置。

一文助你全面理解合成控制方法

转置数据

转置之后,我们计算合成控制权重,就像计算回归系数一样。然而,现在一个观测值是一个时间段,一个特征对应一个单元。

一文助你全面理解合成控制方法

合成控制回归

请注意,此转换不是无辜的。在线性回归中,我们假设外部特征和内部特征之间的关系在各个单元之间是相同的,而在合成控制中,我们假定处理单元和控制单元之间的关系随时间的推移是相同的。

回到自动驾驶汽车案例

现在,让我们回到上面的自动驾驶汽车数据处理步骤。首先,我们编写了一个synth_predict函数,该函数将在控制城市上训练的模型作为输入,并尝试在引入自动驾驶汽车之前预测经过处理的城市迈阿密的结果。

让我们通过线性回归来估计模型。

淘特旅游CMS系统Asp.Net版
淘特旅游CMS系统Asp.Net版

淘特旅游网站管理系统是我们根据多年CMS开发经验,为面向旅游行业专门定制开发的一套旅游网站整体解决方案。系统提供旅游线路、酒店、景点、门票、问答、在线预定、信息采集、SEO优化、点评、会员、广告、财务等近百项业务管理模块。系统采用淘特Asp.NetCms为基础架构,信息发布方便灵活,模板+标签机制,前台信息生成静态HTM文件,确保网站在发展状大同时能安全、稳定。

下载
from sklearn.linear_model import LinearRegression
coef = synth_predict(df, LinearRegression(), treated_city, treatment_year).coef_

我们在迈阿密的预自动驾驶汽车收入方面的表现如何?自动驾驶汽车的潜在影响是什么?

通过绘制迈阿密城市的实际收入和预测收入,我们可以直观地回答这两个问题。

plot_lines(df, treated_city, f'Synthetic {treated_city}', treatment_year)

一文助你全面理解合成控制方法

观测的和合成的Miami收入对照

看起来自动驾驶汽车对迈阿密的收入产生了明显的正面影响:预测的趋势低于实际数据,并且在自动驾驶汽车推出后就出现了偏差。

另一方面,我们显然是过度拟合:实验前预测的收入线与实际数据完全重叠。考虑到迈阿密收入的高度可变性,至少可以说这是可疑的。

另一个问题涉及权重。让我们来设计它们。

df_states = pd.DataFrame({'city': [c for c in cities if c!=treated_city], 'ols_coef': coef})
plt.figure(figsize=(10, 9))
sns.barplot(data=df_states, x='ols_coef', y='city');

一文助你全面理解合成控制方法

合成控制权重

我们的数据中存在许多负权重,从因果推理的角度来看,它们没有多大意义。我可以理解:迈阿密可以用0.2比例的圣路易斯、0.15比例的俄克拉何马和0.15比例的哈特福德的组合来表达。但迈阿密是密尔沃基-0.15比例是什么意思?

因为我们想把我们的合成控制解释为未处理状态的加权平均;所以,所有权重都应该是正数,并且它们的总和应该是1。

为了解决这两个问题(加权和过度拟合),我们需要对权重施加一些限制。

约束权重

为了解决权重过大和负权重的问题,Abadie、Diamond和​​Hainmueller(2010)​​提出了以下权重定义:

一文助你全面理解合成控制方法

权重的约束定义

这意味着,一组权重β:

  • 实验前对照组Xc的加权可观察特征与实验组X的可观察特征相匹配
  • 它们的总和为1
  • 并且不是负的
from toolz import partial
from scipy.optimize import fmin_slsqp

class SyntheticControl():

# Loss function
def loss(self, W, X, y) -> float:
return np.sqrt(np.mean((y - X.dot(W))**2))

# Fit model
def fit(self, X, y):
w_start = [1/X.shape[1]]*X.shape[1]
self.coef_ = fmin_slsqp(partial(self.loss, X=X, y=y),
np.array(w_start),
f_eqcons=lambda x: np.sum(x) - 1,
bounds=[(0.0, 1.0)]*len(w_start),
disp=False)
self.mse = self.loss(W=self.coef_, X=X, y=y)
return self

# Predict
def predict(self, X):
return X.dot(self.coef_)

通过这种方法,我们得到了一个可解释的反事实,即未处理单元的加权平均数。

现在,我们来编写我们自己的目标函数。为此,我创建了一个新类SyntheticControl,它使用了一个如上所述的损失函数,还有一个方法用来拟合它并且预测已处理单元的值。

我们现在可以重复与以前相同的过程,但使用SyntheticControl方法,而不是简单、无约束的LinearRegression方法。

df_states['coef_synth'] = synth_predict(df, SyntheticControl(), treated_city, treatment_year).coef_
plot_lines(df, treated_city, f'Synthetic {treated_city}', treatment_year)

一文助你全面理解合成控制方法

观测的和合成的Miami收入对照

正如我们所看到的,现在已经不再存在过度拟合了。预处理的实际收入和预测收入接近,但不完全相同。原因是非负性约束将大多数系数约束为零,正如​​Lasso​​所做的那样。

现在看起来效果又是负面的。不过,让我们仔细绘制一下两条线之间的差异,以便更好地显示幅度变化。

def plot_difference(df, city, year, vline=True, hline=True, **kwargs):
sns.lineplot(x=df['year'], y=df[city] - df[f'Synthetic {city}'], **kwargs)
if vline:
plt.axvline(x=year, ls=":", color='C2', label='Self-driving cars', lw=3, zorder=100)
plt.legend()
if hline: sns.lineplot(x=df['year'], y=0, lw=3, color='k', zorder=1)
plt.title("Estimated effect of self-driving cars");

请参考存储于GitHub网站工程仓库中的源文件synth_plot_difference.py了解更详细的代码。

plot_difference(df, treated_city, treatment_year)

一文助你全面理解合成控制方法

观测的和合成的Miami收入差异对照图

图中的差异显然是正面的,并且随着时间的推移略有增加。

我们还可以通过可视化权重来解释估计的反事实(如果没有自动驾驶汽车,迈阿密会发生什么)。

plt.figure(figsize=(10, 9))
sns.barplot(data=df_states, x='coef_synth', y='city');

一文助你全面理解合成控制方法

约束权重

正如我们所见,现在我们将迈阿密的收入表示为几个城市的线性组合:坦帕、圣路易斯,以及在较低程度上的拉斯维加斯。通过这种办法,整个过程变得非常透明。

推论

那么,至此我们可以得到什么推论呢?估计值是否与零显著不同?或者,更实际地说,“在没有政策效应的零假设下,这种估计有多不寻常?”。

为了回答这个问题,我们将进行随机性排列测试。我们的想法是,如果这项政策没有效果,我们观察到的迈阿密的效果应该与我们观察的任何其他城市的效果没有显著差异。

因此,我们将复制上述程序;但是,对于所有其他城市来说,将其与迈阿密的估计值进行比较。

fig, ax = plt.subplots()
for city in cities:
synth_predict(df, SyntheticControl(), city, treatment_year)
plot_difference(df, city, treatment_year, vline=False, alpha=0.2, color='C1', lw=3)
plot_difference(df, treated_city, treatment_year)

一文助你全面理解合成控制方法

合成效果分布

从图中,我们注意到两件事。首先,迈阿密的影响非常极端,因此可能不会受到随机噪音的影响。

第二,我们还注意到,有几个城市我们无法很好地拟合之前的趋势。特别是,有一条线明显低于所有其他线。这是意料之中的,因为对于每个城市,我们将反事实趋势构建为所有其他城市的凸组合。在收入方面相当极端的城市对于构建其他城市的反事实非常有用,但很难为它们构建反事实。

为了不使分析产生偏差,让我们排除在预处理MSE方面无法构建“足够好”反事实的状态。

一文助你全面理解合成控制方法

均方误差预测的预处理

根据经验,Abadie、Diamond和​​Hainmueller(2010)​​建议排除预测MSE大于处理单元MSE两倍的单元。

# Reference mse
mse_treated = synth_predict(df, SyntheticControl(), treated_city, treatment_year).mse

# Other mse
fig, ax = plt.subplots()
for city in cities:
mse = synth_predict(df, SyntheticControl(), city, treatment_year).mse
if mse < 2 * mse_treated:
plot_difference(df, city, treatment_year, vline=False, alpha=0.2, color='C1', lw=3)
plot_difference(df, treated_city, treatment_year)

一文助你全面理解合成控制方法

没有不可预测单元时的合成效果分布

排除极端观测后,迈阿密的影响似乎极不寻常。

Abadie、Diamond和Hainmueller(2010)建议进行随机试验的一个统计数据是治疗前MSE和治疗后MSE之间的比率。

一文助你全面理解合成控制方法

前后均方预测误差比

我们可以计算一个p值作为具有更高比率的观测数。

lambdas = {}

对于每一个城市,都执行如下操作:

mse_pre = synth_predict(df, SyntheticControl(), city, treatment_year).mse
mse_tot = np.mean((df[f'Synthetic {city}'] - df[city])**2)
lambdas[city] = (mse_tot - mse_pre) / mse_pre

print(f"p-value: {np.mean(np.fromiter(lambdas.values(), dtype='float') > lambdas[treated_city]):.4}")

请参考存储于GitHub网站工程仓库中的源文件mse_predict.py了解更详细的代码。

p-value: 0.04348

似乎只有4.3%的城市的MSE比迈阿密大,这意味着p值为0.043。我们可以用直方图直观地显示排列下的统计分布。

fig, ax = plt.subplots()
_, bins, _ = plt.hist(lambdas.values(), bins=20, color="C1");
plt.hist([lambdas[treated_city]], bins=bins)
plt.title('Ratio of $MSE_{post}$ and $MSE_{pre}$ across cities');
ax.add_artist(AnnotationBbox(OffsetImage(plt.imread('fig/miami.png'), zoom=0.25), (2.7, 1.7), frameon=False));

一文助你全面理解合成控制方法

各城市前后MSE比率的分布

事实上,迈阿密的统计数据是相当极端的,这也表明观察到的效应不太可能是噪声造成的。

结论

在本文中,我们探索了一种时下非常流行的因果推断方法——合成控制方法,当我们只有很少的处理单元但有很多时间段时常常使用这种方法。这种情形经常出现在行业需求中,此时必须在总体水平上分配处理,而且可能不会进行随机化。合成控制的关键思想是将控制单元组合成一个合成控制单元,用作反事实,以估计处理的因果效应。

合成控制的主要优点之一是,只要我们使用约束是和为1的正权重,该方法就能够避免外推:我们永远不会失去数据的支持。此外,合成控制研究可以“预先注册”:您可以在研究前指定权重,以避免P值篡改(p-hacking)和择优而取(cherry-picking)。这种方法在业界如此流行的另一个原因是权重使反事实分析变得明确:人们可以查看权重并了解我们正在进行的比较。

总体来看,合成控制方法的发展尚处于发展早期,每年都会出现该方法的许多扩展。一些值得关注的扩展包括:​​徐(2017)的广义合成控制​​​、Doudchenko和​​Imbens(2017)提出的差异合成控制​​​、​​Abadie e L(2020)的惩罚合成控制​​​提出的​​矩阵完成方法​​,等等。最后还值得补充的一个重要细节是,如果你想让其发明者之一解释这种方法,那么在Youtube上可以免费获得由Alberto Abadie在NBER暑期研究所做的精彩演讲。

参考文献

[1] A. Abadie, A. Diamond, J. Hainmueller, Synthetic Control Methods for Comparative Case Studies: Estimating the Effect of California’s Tobacco Control Program (2010), Journal of the American Statistical Association.

[2] A. Abadie, Using Synthetic Controls: Feasibility, Data Requirements, and Methodological Aspects (2021), Journal of Economic Perspectives.

[3] N. Doudchenko, G. Imbens, Balancing, Regression, Difference-In-Differences and Synthetic Control Methods: A Synthesis (2017), working paper.

[4] Y. Xu, Generalized Synthetic Control Method: Causal Inference with Interactive Fixed Effects Models (2018), Political Analysis.

[5] A. Abadie, J. L’Hour, A Penalized Synthetic Control Estimator for Disaggregated Data (2020), Journal of the American Statistical Association.

[6] S. Athey, M. Bayati, N. Doudchenko, G. Imbens, K. Khosravi, Matrix Completion Methods for Causal Panel Data Models (2021), Journal of the American Statistical Association.

译者介绍

朱先忠,51CTO社区编辑,51CTO专家博客、讲师,潍坊一所高校计算机教师,自由编程界老兵一枚。早期专注各种微软技术(编著成ASP.NET AJX、Cocos 2d-X相关三本技术图书),近十多年投身于开源世界(熟悉流行全栈Web开发技术),了解基于OneNet/AliOS+Arduino/ESP32/树莓派等物联网开发技术与Scala+Hadoop+Spark+Flink等大数据开发技术。

原文标题:Understanding Synthetic Control Methods,作者:Matteo Courthoud

相关专题

更多
Golang gRPC 服务开发与Protobuf实战
Golang gRPC 服务开发与Protobuf实战

本专题系统讲解 Golang 在 gRPC 服务开发中的完整实践,涵盖 Protobuf 定义与代码生成、gRPC 服务端与客户端实现、流式 RPC(Unary/Server/Client/Bidirectional)、错误处理、拦截器、中间件以及与 HTTP/REST 的对接方案。通过实际案例,帮助学习者掌握 使用 Go 构建高性能、强类型、可扩展的 RPC 服务体系,适用于微服务与内部系统通信场景。

8

2026.01.15

公务员递补名单公布时间 公务员递补要求
公务员递补名单公布时间 公务员递补要求

公务员递补名单公布时间不固定,通常在面试前,由招录单位(如国家知识产权局、海关等)发布,依据是原入围考生放弃资格,会按笔试成绩从高到低递补,递补考生需按公告要求限时确认并提交材料,及时参加面试/体检等后续环节。要求核心是按招录单位公告及时响应、提交材料(确认书、资格复审材料)并准时参加面试。

44

2026.01.15

公务员调剂条件 2026调剂公告时间
公务员调剂条件 2026调剂公告时间

(一)符合拟调剂职位所要求的资格条件。 (二)公共科目笔试成绩同时达到拟调剂职位和原报考职位的合格分数线,且考试类别相同。 拟调剂职位设置了专业科目笔试条件的,专业科目笔试成绩还须同时达到合格分数线,且考试类别相同。 (三)未进入原报考职位面试人员名单。

58

2026.01.15

国考成绩查询入口 国考分数公布时间2026
国考成绩查询入口 国考分数公布时间2026

笔试成绩查询入口已开通,考生可登录国家公务员局中央机关及其直属机构2026年度考试录用公务员专题网站http://bm.scs.gov.cn/pp/gkweb/core/web/ui/business/examResult/written_result.html,查询笔试成绩和合格分数线,点击“笔试成绩查询”按钮,凭借身份证及准考证进行查询。

11

2026.01.15

Java 桌面应用开发(JavaFX 实战)
Java 桌面应用开发(JavaFX 实战)

本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

65

2026.01.14

php与html混编教程大全
php与html混编教程大全

本专题整合了php和html混编相关教程,阅读专题下面的文章了解更多详细内容。

36

2026.01.13

PHP 高性能
PHP 高性能

本专题整合了PHP高性能相关教程大全,阅读专题下面的文章了解更多详细内容。

75

2026.01.13

MySQL数据库报错常见问题及解决方法大全
MySQL数据库报错常见问题及解决方法大全

本专题整合了MySQL数据库报错常见问题及解决方法,阅读专题下面的文章了解更多详细内容。

21

2026.01.13

PHP 文件上传
PHP 文件上传

本专题整合了PHP实现文件上传相关教程,阅读专题下面的文章了解更多详细内容。

35

2026.01.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
550W粉丝大佬手把手从零学JavaScript
550W粉丝大佬手把手从零学JavaScript

共1课时 | 0.2万人学习

PHP自制框架
PHP自制框架

共8课时 | 0.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号