热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Python时间序列数据分析以示例说明

本文的内容主要来源于博客:本人做了适当的注释和补充。https:www.analyticsvidhya.comblog201602time-series-forec


本文的内容主要来源于博客:本人做了适当的注释和补充。


https://www.analyticsvidhya.com/blog/2016/02/time-series-forecasting-codes-python/ 英文不错的读者可以前去阅读原文。

在阅读本文之前 ,推荐先阅读:http://www.cnblogs.com/bradleon/p/6827109.html


导读

本文主要分为四个部分:


  1. 用pandas处理时序数据
  2. 怎样检查时序数据的稳定性
  3. 怎样让时序数据具有稳定性
  4. 时序数据的预测

1. 用pandas导入和处理时序数据

第一步:导入常用的库


import pandas as pd
import numpy as np
import matplotlib.pylab as plt
from matplotlib.pylab import rcParams
#rcParams设定好画布的大小
rcParams['figure.figsize'] = 15, 6

第二步:导入时序数据
数据文件可在github:
http://github.com/aarshayj/Analytics_Vidhya/tree/master/Articles/Time_Series_Analysis 中下载


data = pd.read_csv(path+"AirPassengers.csv")
print data.head()
print '\n Data types:'
print data.dtypes

运行结果如下:数据包括每个月对应的passenger的数目。
可以看到data已经是一个DataFrame,包含两列Month和#Passengers,其中Month的类型是object,而index是0,1,2...
filelist

第三步:处理时序数据
我们需要将Month的类型变为datetime,同时作为index。


dateparse = lambda dates: pd.datetime.strptime(dates, '%Y-%m')
#---其中parse_dates 表明选择数据中的哪个column作为date-time信息,
#---index_col 告诉pandas以哪个column作为 index
#--- date_parser 使用一个function(本文用lambda表达式代替),使一个string转换为一个datetime变量
data = pd.read_csv('AirPassengers.csv', parse_dates=['Month'], index_col='Month',date_parser=dateparse)
print (data.head)
print (data.index)

结果如下:可以看到data的index已经变成datetime类型的Month了。
filelist
filelist


2.怎样检查时序数据的稳定性(Stationarity)

因为ARIMA模型要求数据是稳定的,所以这一步至关重要。


1. 判断数据是稳定的常基于对于时间是常量的几个统计量:


  1. 常量的均值
  2. 常量的方差
  3. 与时间独立的自协方差

用图像说明如下:


  1. 均值
    filelist
    X是时序数据的值,t是时间。可以看到左图,数据的均值对于时间轴来说是常量,即数据的均值不是时间的函数,所有它是稳定的;右图随着时间的推移,数据的值整体趋势是增加的,所有均值是时间的函数,数据具有趋势,所以是非稳定的。
  2. 方差
    filelist
    可以看到左图,数据的方差对于时间是常量,即数据的值域围绕着均值上下波动的振幅是固定的,所以左图数据是稳定的。而右图,数据的振幅在不同时间点不同,所以方差对于时间不是独立的,数据是非稳定的。但是左、右图的均值是一致的。
  3. 自协方差
    filelist
    一个时序数据的自协方差,就是它在不同两个时刻i,j的值的协方差。可以看到左图的自协方差于时间无关;而右图,随着时间的不同,数据的波动频率明显不同,导致它i,j取值不同,就会得到不同的协方差,因此是非稳定的。虽然右图在均值和方差上都是与时间无关的,但仍是非稳定数据。

2. python判断时序数据稳定性

有两种方法:
1.Rolling statistic-- 即每个时间段内的平均的数据均值和标准差情况。


  1. Dickey-Fuller Test -- 这个比较复杂,大致意思就是在一定置信水平下,对于时序数据假设 Null hypothesis: 非稳定。
    if 通过检验值(statistic)<临界值(critical value)&#xff0c;则拒绝null hypothesis&#xff0c;即数据是稳定的&#xff1b;反之则是非稳定的。

from statsmodels.tsa.stattools import adfuller
def test_stationarity(timeseries):#这里以一年为一个窗口&#xff0c;每一个时间t的值由它前面12个月&#xff08;包括自己&#xff09;的均值代替&#xff0c;标准差同理。rolmean &#61; pd.rolling_mean(timeseries,window&#61;12)rolstd &#61; pd.rolling_std(timeseries, window&#61;12)#plot rolling statistics:fig &#61; plt.figure()fig.add_subplot()orig &#61; plt.plot(timeseries, color &#61; &#39;blue&#39;,label&#61;&#39;Original&#39;)mean &#61; plt.plot(rolmean , color &#61; &#39;red&#39;,label &#61; &#39;rolling mean&#39;)std &#61; plt.plot(rolstd, color &#61; &#39;black&#39;, label&#61; &#39;Rolling standard deviation&#39;)plt.legend(loc &#61; &#39;best&#39;)plt.title(&#39;Rolling Mean & Standard Deviation&#39;)plt.show(block&#61;False)#Dickey-Fuller test:print &#39;Results of Dickey-Fuller Test:&#39;dftest &#61; adfuller(timeseries,autolag &#61; &#39;AIC&#39;)#dftest的输出前一项依次为检测值&#xff0c;p值&#xff0c;滞后数&#xff0c;使用的观测数&#xff0c;各个置信度下的临界值dfoutput &#61; pd.Series(dftest[0:4],index &#61; [&#39;Test Statistic&#39;,&#39;p-value&#39;,&#39;#Lags Used&#39;,&#39;Number of Observations Used&#39;])for key,value in dftest[4].items():dfoutput[&#39;Critical value (%s)&#39; %key] &#61; valueprint dfoutputts &#61; data[&#39;#Passengers&#39;]
test_stationarity(ts)

结果如下&#xff1a;


可以看到&#xff0c;数据的rolling均值/标准差具有越来越大的趋势&#xff0c;是不稳定的。
且DF-test可以明确的指出&#xff0c;在任何置信度下&#xff0c;数据都不是稳定的。


3. 让时序数据变成稳定的方法

让数据变得不稳定的原因主要有俩&#xff1a;


  1. 趋势&#xff08;trend&#xff09;-数据随着时间变化。比如说升高或者降低。
  2. 季节性(seasonality)-数据在特定的时间段内变动。比如说节假日&#xff0c;或者活动导致数据的异常。

由于原数据值域范围比较大&#xff0c;为了缩小值域&#xff0c;同时保留其他信息&#xff0c;常用的方法是对数化&#xff0c;取log。


ts_log &#61; np.log(ts)


  1. 检测和去除趋势
    通常有三种方法&#xff1a;

    • 聚合 : 将时间轴缩短&#xff0c;以一段时间内星期/月/年的均值作为数据值。使不同时间段内的值差距缩小。
    • 平滑&#xff1a; 以一个滑动窗口内的均值代替原来的值&#xff0c;为了使值之间的差距缩小
    • 多项式过滤&#xff1a;用一个回归模型来拟合现有数据&#xff0c;使得数据更平滑。

本文主要使用平滑方法


Moving Average--移动平均


moving_avg &#61; pd.rolling_mean(ts_log,12)
plt.plot(ts_log ,color &#61; &#39;blue&#39;)
plt.plot(moving_avg, color&#61;&#39;red&#39;)


可以看出moving_average要比原值平滑许多。

然后作差&#xff1a;


ts_log_moving_avg_diff &#61; ts_log-moving_avg
ts_log_moving_avg_diff.dropna(inplace &#61; True)
test_stationarity(ts_log_moving_avg_diff)



可以看到&#xff0c;做了处理之后的数据基本上没有了随时间变化的趋势&#xff0c;DFtest的结果告诉我们在95%的置信度下&#xff0c;数据是稳定的。

上面的方法是将所有的时间平等看待&#xff0c;而在许多情况下&#xff0c;可以认为越近的时刻越重要。所以引入指数加权移动平均-- Exponentially-weighted moving average.&#xff08;pandas中通过ewma()函数提供了此功能。&#xff09;


# halflife的值决定了衰减因子alpha&#xff1a; alpha &#61; 1 - exp(log(0.5) / halflife)
expweighted_avg &#61; pd.ewma(ts_log,halflife&#61;12)
ts_log_ewma_diff &#61; ts_log - expweighted_avg
test_stationarity(ts_log_ewma_diff)



可以看到相比普通的Moving Average&#xff0c;新的数据平均标准差更小了。而且DFtest可以得到结论&#xff1a;数据在99%的置信度上是稳定的。


  1. 检测和去除季节性
    有两种方法&#xff1a;

    • 1 差分化&#xff1a; 以特定滞后数目的时刻的值的作差
    • 2 分解&#xff1a; 对趋势和季节性分别建模在移除它们

Differencing--差分


ts_log_diff &#61; ts_log - ts_log.shift()
ts_log_diff.dropna(inplace&#61;True)
test_stationarity(ts_log_diff)


如图&#xff0c;可以看出相比MA方法&#xff0c;Differencing方法处理后的数据的均值和方差的在时间轴上的振幅明显缩小了。DFtest的结论是在90%的置信度下&#xff0c;数据是稳定的。

3.Decomposing-分解


#分解(decomposing) 可以用来把时序数据中的趋势和周期性数据都分离出来:
from statsmodels.tsa.seasonal import seasonal_decompose
def decompose(timeseries):# 返回包含三个部分 trend&#xff08;趋势部分&#xff09; &#xff0c; seasonal&#xff08;季节性部分&#xff09; 和residual (残留部分)decomposition &#61; seasonal_decompose(timeseries)trend &#61; decomposition.trendseasonal &#61; decomposition.seasonalresidual &#61; decomposition.residplt.subplot(411)plt.plot(ts_log, label&#61;&#39;Original&#39;)plt.legend(loc&#61;&#39;best&#39;)plt.subplot(412)plt.plot(trend, label&#61;&#39;Trend&#39;)plt.legend(loc&#61;&#39;best&#39;)plt.subplot(413)plt.plot(seasonal,label&#61;&#39;Seasonality&#39;)plt.legend(loc&#61;&#39;best&#39;)plt.subplot(414)plt.plot(residual, label&#61;&#39;Residuals&#39;)plt.legend(loc&#61;&#39;best&#39;)plt.tight_layout()return trend , seasonal, residual


如图可以明显的看到&#xff0c;将original数据 拆分成了三份。Trend数据具有明显的趋势性&#xff0c;Seasonality数据具有明显的周期性&#xff0c;Residuals是剩余的部分&#xff0c;可以认为是去除了趋势和季节性数据之后&#xff0c;稳定的数据&#xff0c;是我们所需要的。


#消除了trend 和seasonal之后&#xff0c;只对residual部分作为想要的时序数据进行处理
trend , seasonal, residual &#61; decompose(ts_log)
residual.dropna(inplace&#61;True)
test_stationarity(residual)



如图所示&#xff0c;数据的均值和方差趋于常数&#xff0c;几乎无波动(看上去比之前的陡峭&#xff0c;但是要注意他的值域只有[-0.05,0.05]之间)&#xff0c;所以直观上可以认为是稳定的数据。另外DFtest的结果显示&#xff0c;Statistic值原小于1%时的Critical value&#xff0c;所以在99%的置信度下&#xff0c;数据是稳定的。


4. 对时序数据进行预测

假设经过处理&#xff0c;已经得到了稳定时序数据。接下来&#xff0c;我们使用ARIMA模型
对数据已经预测。ARIMA的介绍可以见本目录下的另一篇文章。

step1&#xff1a; 通过ACF,PACF进行ARIMA&#xff08;p&#xff0c;d&#xff0c;q&#xff09;的p&#xff0c;q参数估计

由前文Differencing部分已知&#xff0c;一阶差分后数据已经稳定&#xff0c;所以d&#61;1。
所以用一阶差分化的ts_log_diff &#61; ts_log - ts_log.shift() 作为输入。
等价于


yt&#61;YtYt1yt&#61;Yt−Yt−1
作为输入。

先画出ACF,PACF的图像,代码如下&#xff1a;


#ACF and PACF plots:
from statsmodels.tsa.stattools import acf, pacf
lag_acf &#61; acf(ts_log_diff, nlags&#61;20)
lag_pacf &#61; pacf(ts_log_diff, nlags&#61;20, method&#61;&#39;ols&#39;)
#Plot ACF:
plt.subplot(121)
plt.plot(lag_acf)
plt.axhline(y&#61;0,linestyle&#61;&#39;--&#39;,color&#61;&#39;gray&#39;)
plt.axhline(y&#61;-1.96/np.sqrt(len(ts_log_diff)),linestyle&#61;&#39;--&#39;,color&#61;&#39;gray&#39;)
plt.axhline(y&#61;1.96/np.sqrt(len(ts_log_diff)),linestyle&#61;&#39;--&#39;,color&#61;&#39;gray&#39;)
plt.title(&#39;Autocorrelation Function&#39;)#Plot PACF:
plt.subplot(122)
plt.plot(lag_pacf)
plt.axhline(y&#61;0,linestyle&#61;&#39;--&#39;,color&#61;&#39;gray&#39;)
plt.axhline(y&#61;-1.96/np.sqrt(len(ts_log_diff)),linestyle&#61;&#39;--&#39;,color&#61;&#39;gray&#39;)
plt.axhline(y&#61;1.96/np.sqrt(len(ts_log_diff)),linestyle&#61;&#39;--&#39;,color&#61;&#39;gray&#39;)
plt.title(&#39;Partial Autocorrelation Function&#39;)
plt.tight_layout()


图中&#xff0c;上下两条灰线之间是置信区间&#xff0c;p的值就是ACF第一次穿过上置信区间时的横轴值。q的值就是PACF第一次穿过上置信区间的横轴值。所以从图中可以得到p&#61;2&#xff0c;q&#61;2。

step2&#xff1a; 得到参数估计值p&#xff0c;d&#xff0c;q之后&#xff0c;生成模型ARIMA&#xff08;p&#xff0c;d&#xff0c;q&#xff09;
为了突出差别&#xff0c;用三种参数取值的三个模型作为对比。
模型1&#xff1a;AR模型(ARIMA(2,1,0))


from statsmodels.tsa.arima_model import ARIMA
model &#61; ARIMA(ts_log, order&#61;(2, 1, 0))
results_AR &#61; model.fit(disp&#61;-1)
plt.plot(ts_log_diff)
plt.plot(results_AR.fittedvalues, color&#61;&#39;red&#39;)
plt.title(&#39;RSS: %.4f&#39;% sum((results_AR.fittedvalues-ts_log_diff)**2))


图中&#xff0c;蓝线是输入值&#xff0c;红线是模型的拟合值&#xff0c;RSS的累计平方误差。

模型2&#xff1a;MA模型&#xff08;ARIMA&#xff08;0,1,2&#xff09;&#xff09;


model &#61; ARIMA(ts_log, order&#61;(0, 1, 2))
results_MA &#61; model.fit(disp&#61;-1)
plt.plot(ts_log_diff)
plt.plot(results_MA.fittedvalues, color&#61;&#39;red&#39;)
plt.title(&#39;RSS: %.4f&#39;% sum((results_MA.fittedvalues-ts_log_diff)**2))

模型3&#xff1a;ARIMA模型(ARIMA(2,1,2))


model &#61; ARIMA(ts_log, order&#61;(2, 1, 2))
results_ARIMA &#61; model.fit(disp&#61;-1)
plt.plot(ts_log_diff)
plt.plot(results_ARIMA.fittedvalues, color&#61;&#39;red&#39;)
plt.title(&#39;RSS: %.4f&#39;% sum((results_ARIMA.fittedvalues-ts_log_diff)**2))


由RSS&#xff0c;可知模型3--ARIMA&#xff08;2,1,2&#xff09;的拟合度最好&#xff0c;所以我们确定了最终的预测模型。

step3: 将模型代入原数据进行预测
因为上面的模型的拟合值是对原数据进行稳定化之后的输入数据的拟合&#xff0c;所以需要对拟合值进行相应处理的逆操作&#xff0c;使得它回到与原数据一致的尺度。



#ARIMA拟合的其实是一阶差分ts_log_diff&#xff0c;predictions_ARIMA_diff[i]是第i个月与i-1个月的ts_log的差值。
#由于差分化有一阶滞后&#xff0c;所以第一个月的数据是空的&#xff0c;
predictions_ARIMA_diff &#61; pd.Series(results_ARIMA.fittedvalues, copy&#61;True)
print predictions_ARIMA_diff.head()
#累加现有的diff&#xff0c;得到每个值与第一个月的差分&#xff08;同log底的情况下&#xff09;。
#即predictions_ARIMA_diff_cumsum[i] 是第i个月与第1个月的ts_log的差值。
predictions_ARIMA_diff_cumsum &#61; predictions_ARIMA_diff.cumsum()
#先ts_log_diff &#61;> ts_log&#61;>ts_log &#61;> ts
#先以ts_log的第一个值作为基数&#xff0c;复制给所有值&#xff0c;然后每个时刻的值累加与第一个月对应的差值(这样就解决了&#xff0c;第一个月diff数据为空的问题了)
#然后得到了predictions_ARIMA_log &#61;> predictions_ARIMA
predictions_ARIMA_log &#61; pd.Series(ts_log.ix[0], index&#61;ts_log.index)
predictions_ARIMA_log &#61; predictions_ARIMA_log.add(predictions_ARIMA_diff_cumsum,fill_value&#61;0)
predictions_ARIMA &#61; np.exp(predictions_ARIMA_log)
plt.figure()
plt.plot(ts)
plt.plot(predictions_ARIMA)
plt.title(&#39;RMSE: %.4f&#39;% np.sqrt(sum((predictions_ARIMA-ts)**2)/len(ts)))


5.总结

前面一篇文章&#xff0c;总结了ARIMA建模的步骤。
(1). 获取被观测系统时间序列数据&#xff1b;
(2). 对数据绘图&#xff0c;观测是否为平稳时间序列&#xff1b;对于非平稳时间序列要先进行d阶差分运算&#xff0c;化为平稳时间序列&#xff1b;
(3). 经过第二步处理&#xff0c;已经得到平稳时间序列。要对平稳时间序列分别求得其自相关系数ACF 和偏自相关系数PACF&#xff0c;通过对自相关图和偏自相关图的分析&#xff0c;得到最佳的阶层 p 和阶数 q

(4). 由以上得到的d、q、p&#xff0c;得到ARIMA模型。然后开始对得到的模型进行模型检验。



文章出处&#xff1a;http://www.cnblogs.com/bradleon/p/6832867.html


文章出处&#xff1a;http://www.cnblogs.com/bradleon/p/6832867.html

推荐阅读
  • 在该项目中,参与者需结合历史使用模式和天气数据,以预测华盛顿特区自行车共享系统的租赁需求。数据分析部分首先涉及数据的收集,包括用户骑行记录和气象信息,为后续模型构建提供基础。通过深入的数据预处理和特征工程,确保数据质量和模型准确性,最终实现对自行车租赁需求的有效预测。 ... [详细]
  • C#实现文件的压缩与解压
    2019独角兽企业重金招聘Python工程师标准一、准备工作1、下载ICSharpCode.SharpZipLib.dll文件2、项目中引用这个dll二、文件压缩与解压共用类 ... [详细]
  • 如何将TS文件转换为M3U8直播流:HLS与M3U8格式详解
    在视频传输领域,MP4虽然常见,但在直播场景中直接使用MP4格式存在诸多问题。例如,MP4文件的头部信息(如ftyp、moov)较大,导致初始加载时间较长,影响用户体验。相比之下,HLS(HTTP Live Streaming)协议及其M3U8格式更具优势。HLS通过将视频切分成多个小片段,并生成一个M3U8播放列表文件,实现低延迟和高稳定性。本文详细介绍了如何将TS文件转换为M3U8直播流,包括技术原理和具体操作步骤,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 利用Flask框架进行高效Web应用开发
    本文探讨了如何利用Flask框架高效开发Web应用,以满足特定业务需求。具体案例中,一家餐厅希望每天推出不同的特色菜,并通过网站向顾客展示当天的特色菜。此外,还增加了一个介绍页面,在bios路径下详细展示了餐厅主人、厨师和服务员的背景和简介。通过Flask框架的灵活配置和简洁代码,实现了这一功能,提升了用户体验和餐厅的管理水平。 ... [详细]
  • 在尝试对从复杂 XSD 生成的类进行序列化时,遇到了 `NullReferenceException` 错误。尽管已经花费了数小时进行调试和搜索相关资料,但仍然无法找到问题的根源。希望社区能够提供一些指导和建议,帮助解决这一难题。 ... [详细]
  • Python 数据分析领域不仅拥有高质量的开发环境,还提供了众多功能强大的第三方库。本文将介绍六个关键步骤,帮助读者掌握 Python 数据分析的核心技能,并深入探讨六款虽不广为人知但却极具潜力的数据处理库,如 Pandas 的替代品和新兴的可视化工具,助力数据科学家和分析师提升工作效率。 ... [详细]
  • 在第七天的深度学习课程中,我们将重点探讨DGL框架的高级应用,特别是在官方文档指导下进行数据集的下载与预处理。通过详细的步骤说明和实用技巧,帮助读者高效地构建和优化图神经网络的数据管道。此外,我们还将介绍如何利用DGL提供的模块化工具,实现数据的快速加载和预处理,以提升模型训练的效率和准确性。 ... [详细]
  • 运用Isotonic回归算法解决鸢尾花数据集中的回归挑战
    本文探讨了利用Isotonic回归算法解决鸢尾花数据集中的回归问题。首先介绍了Isotonic回归的基本原理及其在保持单调性方面的优势,并通过具体示例说明其应用方法。随后详细描述了鸢尾花数据集的特征和获取途径,最后展示了如何将Isotonic回归应用于该数据集,以实现更准确的预测结果。 ... [详细]
  • MySQL 5.7 学习指南:SQLyog 中的主键、列属性和数据类型
    本文介绍了 MySQL 5.7 中主键(Primary Key)和自增(Auto-Increment)的概念,以及如何在 SQLyog 中设置这些属性。同时,还探讨了数据类型的分类和选择,以及列属性的设置方法。 ... [详细]
  • MySQL Decimal 类型的最大值解析及其在数据处理中的应用艺术
    在关系型数据库中,表的设计与SQL语句的编写对性能的影响至关重要,甚至可占到90%以上。本文将重点探讨MySQL中Decimal类型的最大值及其在数据处理中的应用技巧,通过实例分析和优化建议,帮助读者深入理解并掌握这一重要知识点。 ... [详细]
  • 如何在MySQL中选择合适的表空间以优化性能和管理效率
    在MySQL中,合理选择表空间对于提升表的管理和访问性能至关重要。表空间作为MySQL中用于组织和管理数据的一种机制,能够显著影响数据库的运行效率和维护便利性。通过科学地配置和使用表空间,可以优化存储结构,提高查询速度,简化数据管理流程,从而全面提升系统的整体性能。 ... [详细]
  • 在C#中开发MP3播放器时,我正在考虑如何高效存储元数据以便快速检索。选择合适的数据结构,如字典或数组,对于优化性能至关重要。字典能够提供快速的键值对查找,而数组则在连续存储和遍历方面表现优异。根据具体需求,合理选择数据结构将显著提升应用的响应速度和用户体验。 ... [详细]
  • 在MySQL中实现时间比较功能的详细解析与应用
    在MySQL中实现时间比较功能的详细解析与应用。本文深入探讨了MySQL中时间比较的实现方法,重点介绍了`UNIX_TIMESTAMP`函数的应用。该函数可以接收一个日期时间参数,也可以不带参数使用,其返回值为Unix时间戳,便于进行时间的精确比较和计算。此外,文章还涵盖了其他相关的时间处理函数和技巧,帮助读者更好地理解和掌握MySQL中的时间操作。 ... [详细]
  • 内网渗透技术详解:PTH、PTT与PTK在域控环境中的应用及猫盘内网穿透配置
    本文深入探讨了内网渗透技术,特别是PTH、PTT与PTK在域控环境中的应用,并详细介绍了猫盘内网穿透的配置方法。通过这些技术,安全研究人员可以更有效地进行内网渗透测试,解决常见的渗透测试难题。此外,文章还提供了实用的配置示例和操作步骤,帮助读者更好地理解和应用这些技术。 ... [详细]
  • 决策树在鸢尾花数据集上对不同特征组合的分类效果分析及模型性能比较
    本文探讨了决策树算法在鸢尾花数据集上的应用,分析了不同特征组合对分类效果的影响,并对模型性能进行了详细比较。决策树作为一种层次化的分类方法,通过递归地划分特征空间,形成树状结构,每个节点代表一个特征判断,最终达到分类目的。研究结果表明,不同特征组合对模型性能有显著影响,为实际应用提供了重要参考。 ... [详细]
author-avatar
吴雨醒
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有