简介:这份资源面向具备一定机器学习与Python基础的数据分析学习者,围绕旅游与经济指标之间的关联展开完整实战。包内共9个文件,以7个py源代码为主,另含1个csv数据集与1个txt说明文件,压缩包约283KB,数据文件约665.76KB,覆盖从数据读取、清洗到建模预测的全流程。代码手工整理、无语法错误,可直接运行,涉及pandas、seaborn、matplotlib等可视化与数据处理模块,并调用scikit-learn、xgboost、statsmodels、geopandas等库,涵盖线性回归、随机森林、梯度提升、SVR、决策树、KMeans聚类、ARIMA与指数平滑等模型,还包含t检验等统计方法。读者可借此掌握跨国旅游与经济数据的探索性分析、特征标准化、模型对比与时间序列预测思路,理解如何用多种算法评估旅游对经济的影响。目前已有40人学习下载,适合作为课程设计、竞赛练习或自学项目的参考案例。
1. 旅游和经济指标关系数据集分析:一份能直接跑通的实战资源
拿到一份写着「AI实战」的压缩包,最怕的就是解压之后一堆脚本互相依赖、路径写死、跑起来报错。这次拆的这份旅游与经济指标关系数据集分析预测实例,结构上算是比较克制的:7 个 Python 脚本加 1 个 CSV 数据文件,数据量 665.76 KB,代码总量 44.34 KB,没有乱七八糟的中间产物。它要解决的问题很具体——把全球旅游指标和宏观经济指标放在一张表里,做探索性分析、相关性检验、聚类分群,再上回归和时序模型做预测。适合两类人:一类是想找一个完整 EDA 到建模闭环练手的机器学习初学者,另一类是手里有类似「行业指标 + 经济指标」宽表、想参考别人怎么组织分析流程的从业者。数据文件world_tourism_economy_data.csv是整份资源的锚点,所有脚本都围绕它展开,这一点比那些数据散落在多个文件里的包友好得多。
2. 数据表结构与七个脚本的分工:先看清家底再动手
2.1 宽表字段与建模可用性判断
world_tourism_economy_data.csv是一张典型的面板宽表,粒度是「国家-年份」。常见字段包括国家名称、年份、国际游客到访量、旅游收入、旅游支出、GDP、人均 GDP、通胀率、失业率等。这类表在建模前必须先做三件事:确认国家名称的写法是否统一、确认年份范围是否连续、确认缺失值是按国家整段缺还是零星缺。
国家名称不统一是这类数据最常见的坑。脚本里引入了pycountry,作用就是把各种写法的国家名映射到标准 ISO 编码。如果你跳过这一步直接按国家名分组,会出现「United States」和「United States of America」被当成两个国家的情况,聚合结果直接翻车。
import pandas as pd import pycountry df = pd.read_csv("data/world_tourism_economy_data.csv") print(df.shape) print(df.columns.tolist()) print(df.isnull().sum().sort_values(ascending=False).head(10)) # 国家名标准化:把常见别名映射到 ISO alpha_3 def normalize_country(name): try: return pycountry.countries.lookup(name).alpha_3 except LookupError: return None df["country_code"] = df["country"].apply(normalize_country) print(df["country_code"].isnull().sum()) # 看有多少没匹配上这段代码的逻辑是先摸清形状和缺失分布,再做国家名标准化。lookup匹配不上会抛LookupError,所以用 try 兜住返回 None,最后统计 None 的数量。如果没匹配上的行数占比超过 5%,就要手动补一张别名映射表,而不是直接丢掉。
2.2 七个脚本各自负责什么
从文件名能大致还原出分析链路。1-Tourism and Economic Impact Analysis.py是总入口式的分析,做基础统计和可视化;3-TOURISM.py偏数据清洗和指标构造;4-Tourism and Economic Impact EDA eg china.py是以中国为例的单国 EDA;5-Analyzing Global Tourisms Economic Effects.py做全球层面的经济效应分析;6-EDA Tourism.py是更完整的探索性分析;7-Global Tourism Indicators and Economic Dynamics.py和8-Global Tourism Economic Analysis.py偏向建模和动态关系。
这种编号方式说明作者是按分析深度递进的,不是七个互不相干的 demo。实际使用时建议按编号顺序读,但不必按顺序跑——因为每个脚本大概率都能独立加载 CSV 并输出自己的图表。先跑1和6建立整体印象,再挑4看单国案例,最后跑7、8看建模部分。
2.3 环境依赖与最小可跑配置
模块清单里既有sklearn、xgboost、statsmodels这类建模库,也有geopandas这种地理可视化库。geopandas在 Windows 上安装经常出问题,如果只是跑分析不想折腾地图,可以先把涉及地理绘图的代码注释掉,不影响核心建模流程。
pip install pandas numpy matplotlib seaborn scikit-learn statsmodels xgboost pycountry scipy # geopandas 单独处理,conda 环境下更稳 conda install -c conda-forge geopandaskagglehub出现在依赖里,说明原始数据可能来自 Kaggle,脚本里或许有在线拉取数据的逻辑。但压缩包已经带了完整 CSV,建议把kagglehub相关调用改成直接读本地文件,避免网络问题导致跑不通。这是离线复现这类资源时最省事的一步。
3. 从 EDA 到建模:相关性检验、聚类与回归怎么落地
3.1 相关性分析与 t 检验的正确打开方式
旅游指标和经济指标之间的关系,最容易犯的错误是拿所有国家所有年份混在一起算一个皮尔逊相关系数。面板数据里国家之间的体量差异极大,卢森堡的旅游收入和美国的旅游收入不在一个量级,混算出来的相关性会被大国主导。
更稳妥的做法是先按国家分组算相关系数,再看分布;或者对指标做标准化后再算。脚本里用到了scipy.stats.ttest_ind,这是做两组均值差异检验的。常见用法是把国家按收入水平分成两组,检验两组的旅游收入均值是否有显著差异。
from scipy.stats import ttest_ind from sklearn.preprocessing import StandardScaler # 按人均GDP中位数把国家分成高低两组 median_gdp = df["gdp_per_capita"].median() high = df[df["gdp_per_capita"] > median_gdp]["tourism_receipts"].dropna() low = df[df["gdp_per_capita"] <= median_gdp]["tourism_receipts"].dropna() t_stat, p_val = ttest_ind(high, low, equal_var=False) print(f"t={t_stat:.3f}, p={p_val:.4f}") # 标准化后再看相关性,避免量纲干扰 scaler = StandardScaler() cols = ["tourism_receipts", "gdp_per_capita", "inflation", "unemployment"] df_scaled = pd.DataFrame(scaler.fit_transform(df[cols].dropna()), columns=cols) print(df_scaled.corr())ttest_ind里equal_var=False是 Welch t 检验,不假设两组方差相等,在样本量不等时比默认的 Student t 检验更稳。标准化用StandardScaler做 z-score,之后corr()出来的系数才不会被量纲带偏。这一步很多新手会跳过,导致相关性矩阵看起来全是 0.9 以上,其实是量纲造成的假象。
3.2 KMeans 聚类做国家分群
sklearn.cluster.KMeans在这份资源里的作用是把国家按旅游和经济特征分成几档。聚类前必须标准化,否则 GDP 这种大数值字段会完全主导距离计算。聚类数 K 的选择,脚本里可能写死了,但实际用的时候建议跑一下肘部法。
from sklearn.cluster import KMeans import matplotlib.pyplot as plt features = ["tourism_receipts", "gdp_per_capita", "inflation", "unemployment"] X = StandardScaler().fit_transform(df[features].dropna()) inertia = [] for k in range(2, 9): km = KMeans(n_clusters=k, random_state=42, n_init=10) km.fit(X) inertia.append(km.inertia_) plt.plot(range(2, 9), inertia, marker="o") plt.xlabel("K") plt.ylabel("Inertia") plt.show()n_init=10是显式指定多次初始化取最优,新版 sklearn 默认值变过,写死更保险。random_state=42保证每次跑结果一致,做分析记录时这点很重要。肘部法看的是 inertia 下降速度明显变缓的那个点,通常落在 3 到 5 之间。聚类结果本身没有对错,关键是分出来的组能不能用业务语言解释——比如「高收入低旅游依赖」「中等收入高旅游增长」这样的标签才有意义。
3.3 回归模型对比与误差指标
资源里出现了LinearRegression、RandomForestRegressor、GradientBoostingRegressor、DecisionTreeRegressor、SVR和xgboost,基本覆盖了表格数据回归的常用模型。用train_test_split切分后,用mean_squared_error和mean_absolute_error评估。
from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestRegressor from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, mean_absolute_error X = df[["gdp_per_capita", "inflation", "unemployment"]].dropna() y = df.loc[X.index, "tourism_receipts"] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) for name, model in [ ("Linear", LinearRegression()), ("RF", RandomForestRegressor(n_estimators=200, random_state=42)), ]: model.fit(X_train, y_train) pred = model.predict(X_test) print(name, "MSE:", mean_squared_error(y_test, pred), "MAE:", mean_absolute_error(y_test, pred))n_estimators=200是随机森林的树数量,太少会欠拟合,太多训练慢且收益递减,200 是个常用起点。MSE 对大误差敏感,MAE 更直观,两个一起看能判断模型是被少数极端值带偏还是整体偏差大。线性模型在这里的作用是当基线,如果随机森林比线性好不了多少,说明特征和目标的非线性关系不强,没必要上复杂模型。
4. 时序预测与地理可视化:ARIMA、指数平滑和 geopandas
4.1 ARIMA 与 Holt-Winters 的适用边界
statsmodels里的ARIMA和SimpleExpSmoothing是用来做时间序列预测的。面板数据做时序预测有个前提:得先筛出单个国家的时间序列,不能拿全球汇总数据直接套。ARIMA 的order=(p,d,q)三个参数分别对应自回归阶数、差分次数、移动平均阶数,d通常通过 ADF 检验确定。
from statsmodels.tsa.arima.model import ARIMA from statsmodels.tsa.holtwinters import SimpleExpSmoothing # 取单个国家的年度旅游收入序列 ts = df[df["country"] == "China"].sort_values("year").set_index("year")["tourism_receipts"].dropna() # ARIMA(1,1,1) 作为起点 model = ARIMA(ts, order=(1, 1, 1)) fit = model.fit() print(fit.summary()) forecast = fit.forecast(steps=3) print(forecast) # 指数平滑作为对照 es = SimpleExpSmoothing(ts).fit() print(es.forecast(3))order=(1,1,1)不是万能起点,但作为第一次跑通是合理的。fit.summary()里的 AIC 可以用来比较不同 order 的优劣,AIC 越小越好。SimpleExpSmoothing适合没有明显趋势和季节性的序列,如果序列有趋势,应该用Holt而不是SimpleExpSmoothing。这份资源里用的是 Simple 版本,说明作者可能只做了短期平滑预测,没处理趋势项,这是使用时需要自己补的地方。
4.2 geopandas 地理可视化的替代方案
geopandas用来画国家层面的指标分布图,效果直观但安装门槛高。如果装不上,可以用plotly的 choropleth 或者直接用matplotlib画条形图替代,分析结论不受影响。
import geopandas as gpd import matplotlib.pyplot as plt world = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres")) merged = world.merge(df.groupby("country_code")["tourism_receipts"].mean().reset_index(), left_on="iso_a3", right_on="country_code", how="left") merged.plot(column="tourism_receipts", legend=True, cmap="OrRd") plt.title("Average Tourism Receipts by Country") plt.show()naturalearth_lowres是 geopandas 自带的数据集,不需要额外下载。merge时用iso_a3和前面标准化出来的country_code对齐,这一步如果对不上,地图上会有大片灰色。cmap="OrRd"是橙色渐变,适合表示数值高低。如果gpd.datasets在新版本里被移除了,改用geodatasets包获取底图。
4.3 模型结果怎么验证不是自嗨
跑完回归和时序,最容易自我感觉良好,但模型可能只是记住了训练集。验证要做两件事:一是看残差是否随机分布,二是做时间上的外推测试。回归模型可以用交叉验证,时序模型必须按时间切分,不能用随机切分。
from sklearn.model_selection import TimeSeriesSplit from sklearn.ensemble import GradientBoostingRegressor tscv = TimeSeriesSplit(n_splits=5) model = GradientBoostingRegressor(random_state=42) scores = [] for train_idx, test_idx in tscv.split(X): model.fit(X.iloc[train_idx], y.iloc[train_idx]) scores.append(model.score(X.iloc[test_idx], y.iloc[test_idx])) print("TimeSeriesSplit R2:", scores)TimeSeriesSplit保证训练集永远在测试集之前,符合时间序列的因果性。n_splits=5表示切 5 折。如果各折的 R2 波动很大,说明模型对时间段敏感,泛化能力存疑。这一步是区分「跑通了」和「可信」的关键,很多资源包不会写,但实际用的时候必须补。
5. 避坑与排查:这份资源跑不起来时先看这几条
5.1 现象:FileNotFoundError找不到 CSV
原因:脚本里的路径大概率写的是相对路径data/world_tourism_economy_data.csv,但你的工作目录不是压缩包解压后的根目录。解决:在脚本开头统一加os.chdir(os.path.dirname(os.path.abspath(__file__))),或者把 CSV 路径改成绝对路径。os模块已经在依赖里,直接用。
5.2 现象:pycountry匹配大量返回 None
原因:数据里的国家名可能是「Korea, Rep.」这种世界银行风格写法,pycountry的lookup认不出来。解决:先打印出所有没匹配上的国家名,手动建一个字典映射,比如{"Korea, Rep.": "KOR", "Russian Federation": "RUS"},在normalize_country里先查字典再走lookup。
5.3 现象:geopandas导入报 DLL 错误
原因:Windows 上geopandas依赖的fiona和gdal版本不匹配。解决:用 conda 装而不是 pip,conda install -c conda-forge geopandas会自动解决依赖。如果还是不行,直接跳过地理可视化部分,用matplotlib画国家排序条形图,分析结论一样能出来。
5.4 现象:ARIMA 报「矩阵非正定」或收敛警告
原因:序列太短或者差分后接近常数,导致参数估计不稳定。解决:先检查序列长度,少于 20 个点的年度序列不建议上 ARIMA。可以改用SimpleExpSmoothing或者把order降到(0,1,0)即简单差分。另外确认序列里没有全 0 或全相同值的段。
5.5 现象:聚类结果每次跑都不一样
原因:KMeans没设random_state,初始化中心随机。解决:加上random_state=42和n_init=10。如果加了还是不稳定,说明数据本身簇边界模糊,这时候应该回到特征选择,而不是继续调聚类参数。
6. 进阶用法:把单国分析扩展成面板回归与预测对比
单国 EDA 和全球汇总分析之间,缺一层面板回归。面板数据可以用固定效应模型控制国家间的不可观测差异,比混合回归更干净。statsmodels里没有直接的面板回归,但可以用虚拟变量近似。
import statsmodels.api as sm # 构造国家虚拟变量,近似固定效应 dummies = pd.get_dummies(df["country"], prefix="c", drop_first=True) X_panel = pd.concat([df[["gdp_per_capita", "inflation", "unemployment"]], dummies], axis=1) X_panel = sm.add_constant(X_panel) y_panel = df["tourism_receipts"] model = sm.OLS(y_panel, X_panel, missing="drop").fit() print(model.summary())drop_first=True避免虚拟变量陷阱,add_constant加截距项。missing="drop"自动处理缺失值。看summary()里各经济指标的系数和 p 值,比混合回归的系数更可信,因为国家固定效应被吸收了。
另一个进阶方向是把多个模型的预测结果做对比表,用同一测试集评估。我一般会固定random_state,把线性、随机森林、XGBoost、SVR 跑一遍,记录 MSE 和 MAE,然后看哪个模型在误差和可解释性之间平衡最好。XGBoost 通常误差最低,但线性模型的系数能直接解释弹性,做汇报时反而更有用。
from xgboost import XGBRegressor from sklearn.svm import SVR models = { "XGB": XGBRegressor(n_estimators=300, learning_rate=0.05, random_state=42), "SVR": SVR(kernel="rbf"), } for name, m in models.items(): m.fit(X_train, y_train) pred = m.predict(X_test) print(name, "MSE:", mean_squared_error(y_test, pred))learning_rate=0.05配合n_estimators=300是 XGBoost 常用的慢学习率多树组合,比默认的 0.3 更不容易过拟合。SVR 的kernel="rbf"适合非线性关系,但对特征缩放敏感,前面必须标准化。
从那以后我每次拿到这类「指标 + 经济」宽表,都强制先跑一遍国家名标准化和缺失分布,再决定用哪些字段建模。这份资源的七个脚本给了一条现成的分析链路,但真正让结果可信的,是补上它没写的验证步骤。希望帮到你。
本文还有配套的精品资源,点击获取