数据科学有氧运动 1 - 天气

2025-06-07

数据科学有氧运动 1 - 天气

我打算毫不客气地借用Wes Bos 的 JavaScript30 课程中“编程有氧”的概念。我想,如果我提出一个简短的数据科学示例问题,然后和你们一起解决,你们可能会喜欢。我现在正在辅导一个学习 Python 的学生(我的第一个学生!太棒了!),这就是她的一个问题。这个例子似乎涵盖了很多基础知识。这个例子将使用 Python (3)。我会尽量在合适的库和文档出现时提供链接,这样你们就可以进一步探索,而不是轻信我的空话。

那么,事不宜迟,让我们开始吧!

0. 问题

我们被要求调查各种天气现象如何随纬度变化。具体来说,我们需要收集至少500个天气数据样本,随机分布在全球各地。获得这些数据后,我们应该绘制图表,并分析温度、湿度、云量和风速方面观察到的任何模式。我将把它们转换成美国常用单位制。你觉得怎样做就怎样做吧。

注意:有很多不同的方法可以解决这个问题。我将向您展示一种方法。您可以自由探索您自己的解决方法,看看结果是否相似。

第二点说明:我使用了一些不属于标准库的库,但它们可以在 Python 包索引 (PyPI) 中找到。如果遇到No module named 'whatever'错误,您需要打开一个终端窗口并输入pip install <packagename>,其中<packagename>是您缺少的包的名称,然后按 Enter。如果您使用的是 Jupyter Notebook,也可以输入! pip install <packagename>一个单元格并运行它。感叹号 ( !) 可以让 Notebook 运行一行系统调用。

我最初使用Jupyter Notebook完成了这项分析。我强烈推荐它。如果你有点焦躁,想提前了解一下,可以在这里找到源代码仓库。

1. 500 个随机坐标

我们首先需要的是 500 个随机坐标。我们需要这些数字涵盖所有可能的纬度范围(-90 度到 90 度),以及所有可能的经度范围(-180 度到 180 度)。注意,负纬度表示南纬,负经度表示西纬。

import numpy as np
import pandas as pd

np.random.seed(125)  # So that other scientists can duplicate our work!
lats = np.random.randint(-90, 90, size=500)
longs = np.random.randint(-180, 180, size=500)
coords = pd.DataFrame({
    "latitude": lats,
    "longitude": longs
})

# Let's take a look at how our coordinates look
coords.head()
Enter fullscreen mode Exit fullscreen mode
纬度 经度
0 67 -117
1 -3 11
2 -23 -146
3 20 -19
4 -47 6

为了理智起见,让我们确保我们的坐标是合理随机的。

from matplotlib import pyplot as plt
# And, we're going to give our plots a bit of pizazz.
# Feel free to skip these two lines
import seaborn

seaborn.set()

plt.hist(coords['latitude'])
plt.show()
Enter fullscreen mode Exit fullscreen mode

纬度直方图

plt.hist(coords['longitude'])
plt.show()
Enter fullscreen mode Exit fullscreen mode

经度直方图

虽然有一些峰值,但总体来说,对于我们目前的做法来说,这似乎是合理的。如果您对数据的随机性不满意,请更改随机种子并重新运行上面的单元格。

2. 设置天气 API

这部分会更侧重于管理,编程则没那么有趣。不过没关系!为了获取这些天气数据,我们需要访问一个 Web API 并请求它提供数据。具体来说,我们将使用OpenWeatherData API。您需要创建一个帐户(免费!),然后会获得一个 API 密钥,您可以在帐户页面的“API 密钥”选项卡中找到它。

帐户页面上的 API 选项卡

一定要保密这个密钥(我会给你一些建议,教你如何做好这件事)。你肯定不希望某个不法之徒攻击天气 API,让所有人都以为是你干的。想想你作为天气 API 的良好公民的声誉吧!想想孩子们!

正如页面所示,您的密钥可能需要一些时间才能生效。幸运的是,在使用它之前,我们还需要进行一些设置。现在,我们先来看看要使用的端点。请查看坐标天气端点文档

我们可以通过几种不同的方式请求我们需要的数据,但由于我们已经创建了一堆漂亮的(纬度,经度)对,我认为这可能是最简单的方法。

http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&APPID={api_key}
Enter fullscreen mode Exit fullscreen mode

你会注意到,即使在线文档没有直接讨论,我们也需要添加APPID包含 API 密钥的参数。如果你觉得很酷,还可以添加units=imperial获取华氏温度和英里/小时风速的参数。你也可以保留默认值,稍后再进行转换。我也会向你演示这个过程。好了,管理工作就到此为止!让我们回到代码!

3. 设置以获取数据

在我们打开分析代码之前,我建议您在同一目录中打开一个名为 的新文件secrets.py

# secrets.py
API_KEY = "copy your api key here"
Enter fullscreen mode Exit fullscreen mode

如果您使用 git 存储库跟踪此项目,请将此文件添加到您的.gitignore文件中。

__pycache__/
.ipynb_checkpoints
secrets.py
haters
Enter fullscreen mode Exit fullscreen mode

现在我们准备重新回到笔记本中。

from secrets import API_KEY
import requests
import time

def get_weather_data(coords, time_between=1):
    """Queries the OpenWeatherAPI for data.

    Args:
        coords: A Pandas DataFrame with rows containing 'latitude'
            and 'longitude' columns.
        time_between: An integer specifying the sleep time in seconds
            between each API ping.  Defaults to the OpenWeatherAPI's
            recommended limit of 1 request per second.

    Returns:
        A list of nested dicts (loaded JSON results).
    """
    results = []
    for ind, row in coords.iterrows():
        lat, lon = row['latitude'], row['longitude']
        query = f"http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&APPID={API_KEY}"
        response = requests.get(query)
        results.append(response.json())
        time.sleep(time_between)
    return results
Enter fullscreen mode Exit fullscreen mode

这段代码有两个关键特性。第一个是“f-string”,它是 Python 3 用于字符串插值的 shwoopy 语法。优点是这些“f-string”超级快!至少相对而言是这样。而且,我们可以直接从 DataFrame 行插入经纬度值,以及 API 密钥。

另一个关键特性是,我们使用requests发起get请求,然后使用json函数立即将响应处理成我们可以处理的 Python 字典。如果你不确定如何从 API 获取数据,你可能会失望,因为它并没有比这更复杂。只要你知道正确的 URL,requests我们的工作就会非常轻松。

3a. 记录我们的请求

我要快速说两句题外话,再练习一下。如果你想直接跳到第四步,别担心。你不会伤害我的。

我接下来要做的第一件事是设置一些日志记录到文件中。在笔记本顶部,添加以下代码。

import logging

logger = logging.getLogger('weather')
logger.setLevel(logging.INFO)
fh = logging.FileHandler('api_calls.log')
formatter = logging.Formatter('%(asctime)s - %(message)')
fh.setFormatter(formatter)
logger.addHandler(fh)
Enter fullscreen mode Exit fullscreen mode

然后在你的get_weather_data函数内部:

def get_weather_data(coords, time_between=1):
    # ...
    results = []
    for ind, row in coords.iterrows():
        lat, long = row['latitude'], row['longitude']
        query = f"http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&APPID={API_KEY}"

        # Here's the new stuff
        clean_url = query.rpartition("&")[0]  # Don't log your api key!
        logger.info(f"Call {ind}: ({lat}, {lon}) - {clean_url}")

        response = requests.get(query)
        results.append(response.json())
        time.sleep(time_between)
    return results
Enter fullscreen mode Exit fullscreen mode

现在我们可以保存所有访问过的 URL 的日志!

3b. 获取最接近的城市名称

你知道什么会很棒吗?用我们的日志输出最近的城市名称。有一个简洁的小库citipy可以做到这一点!让我们get_weather_data再次更新我们的函数。

from secrets import API_KEY
from citipy import citipy  # Make sure to import it once you've installed it

def get_weather_data(coords, time_between=1):
    # ...
    results = []
    for ind, row in coords.iterrows():
        lat, lon = row['latitude'], row['longitude']
        query = f"http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&APPID={API_KEY}"
        clean_url = query.rpartition("&")[0]

        # Here's the new stuff
        city = citipy.nearest_city(lat, lon)
        logger.info(f"Call {ind}: {city.city_name} {clean_url})")

        result = requests.get(query)
        results.append(result.json())
        time.sleep(time_between)
    return results
Enter fullscreen mode Exit fullscreen mode

这太棒了!回到手头的问题。

4. 实际获取数据

首先,让我们通过测试调用来测试我们的功能。

test_coords = pd.DataFrame({"latitude": [37], "longitude": [-122]})
test_results = get_weather_data(test_coords)
test_results
Enter fullscreen mode Exit fullscreen mode
[{'base': 'stations',
  'clouds': {'all': 1},
  'cod': 200,
  'coord': {'lat': 37, 'lon': -122},
  'dt': 1522341300,
  'id': 5381421,
  'main': {'humidity': 76,
   'pressure': 1021,
   'temp': 287.78,
   'temp_max': 289.15,
   'temp_min': 286.15},
  'name': 'Pasatiempo',
  'sys': {'country': 'US',
   'id': 399,
   'message': 0.004,
   'sunrise': 1522331815,
   'sunset': 1522376913,
   'type': 1},
  'visibility': 16093,
  'weather': [{'description': 'clear sky',
    'icon': '01d',
    'id': 800,
    'main': 'Clear'}],
  'wind': {'deg': 331.003, 'speed': 1.32}}]
Enter fullscreen mode Exit fullscreen mode

如果您的结果与我的一样,那么看起来我们可以进行完整的数据收集。

full_results = get_weather_data(coords)
full_results[:3]  # Let's peek at the first 3 datapoints
Enter fullscreen mode Exit fullscreen mode

这大约需要8分30秒(这是成为好公民的代价)。去喝杯咖啡或吃点零食,犒劳一下自己所有的辛勤工作吧。

5.保存数据

第一件事是,先把数据保存起来,万一发生爆炸,我们还能用。

import json

with open("weather.json", "w") as outfile:
    json.dump(full_results, outfile)
Enter fullscreen mode Exit fullscreen mode

这将在你的项目目录中创建一个新文件weather.json。接下来是另一个可选的辅助步骤:单位转换。

5a. 单位换算

如果您没有units=imperial在 API 调用中使用该参数并且想要美国习惯单位,那么您将需要一些辅助函数。

def k_to_f(temp):
    """Converts a Kelvin temperature to Fahrenheit"""
    return temp * 9/5 - 459.67

def mps_to_mph(speed):
    """Converts a meters/s speed to miles/hour"""
    return speed * 2.23694
Enter fullscreen mode Exit fullscreen mode

6. 数据处理

是的,这是一个单词。查一下。随便。我们需要构建一个可以转换成 DataFrame 的数据结构,并且希望将范围缩小到我们关心的数据。再看看上面的示例输出,并深入研究 JSON 数据。

important_json_data = []
for point in full_results:
    lat = point['coord']['lat']
    lon = point['coord']['lon']
    temp = k_to_f(point['main']['temp'])
    humidity = point['main']['humidity']
    cloudiness = point['clouds']['all']
    wind = mps_to_mph(point['wind']['speed'])

    row = [lat, lon, temp, humidity, cloudiness, wind]
    important_json_data.append(row)

weather_df = pd.DataFrame(important_json_data)
weather_df.columns = [
    "latitude",
    "longitude",
    "temperature",
    "humidity",
    "clouds",
    "wind",
]
weather_df.head()
Enter fullscreen mode Exit fullscreen mode
纬度 经度 温度 湿度
0 67 -117 -16.15 69 8 4.29
1 -3 11 74.93 96 68 2.17
2 -23 -146 80.96 100 88 12.91
3 20 -19 67.37 100 8 9.78
4 -47 6 44.78 97 8 13.35

再次,为了以防万一,我们还是保存一下数据吧。

weather_df.to_csv("weather.csv")
Enter fullscreen mode Exit fullscreen mode

恭喜!最难的部分完成了。让我们看看数据,看看能得出什么结论。

7.绘制数据

还记得我们的目标吗?

  1. 比较温度和纬度。
  2. 比较湿度和纬度。
  3. 比较云量和纬度。
  4. 比较风速和纬度。
  5. 得出一些结论。

我打算把纬度放在 Y 轴上,因为我觉得这样绘图会更直观。我们通常认为纬度是从北向南,也就是从上向下。如果你想坚持把自变量(纬度)放在 X 轴,把因变量(温度)放在 Y 轴上,那就随心所欲吧。

温度

plt.scatter(weather_df.temperature, weather_df.latitude)
plt.xlabel("Temperature (F)")
plt.ylabel("Latitude (degrees)")
plt.title("Temperature vs. Latitude")
plt.show()
Enter fullscreen mode Exit fullscreen mode

温度纬度图

哇哦!这趋势真强劲!正如你可能预料的那样,温度随着接近赤道而上升,随着接近两极而下降。加油,科学!

湿度

plt.scatter(weather_df.humidity, weather_df.latitude)
plt.xlabel("Humidity (%)")
plt.ylabel("Latitude (degrees)")
plt.title("Humidity vs. Latitude")
plt.show()
Enter fullscreen mode Exit fullscreen mode

湿度与纬度图

这些结果很奇怪。看起来,除了少数几个下降点外,很多数据点的湿度都是100%。我觉得这难以置信。我在谷歌上找到了一些结果,让我怀疑他们测量湿度的方式是不是有什么问题。如果有人有其他想法,我很想听听。请告诉我你的想法。

云量

plt.scatter(weather_df.clouds, weather_df.lat)
plt.xlabel("Cloudiness (%)")
plt.ylabel("Latitude (degrees)")
plt.title("Cloudiness vs. Latitude")
plt.show()
Enter fullscreen mode Exit fullscreen mode

云量与纬度图

我在这里也看不出什么趋势。不过,数据的条纹(整齐的行)让我感觉好像有某种规律。我们看看是否存在经度关系。

plt.scatter(weather_df.long, weather_df.clouds)
plt.xlabel("Longitude (degrees)")
plt.ylabel("Cloudiness (%)")
plt.title("Longitude vs. Cloudiness")
plt.show()
Enter fullscreen mode Exit fullscreen mode

经度云量图

嗯……我还是没看出什么关系。再说一次,如果有人有什么想法,请告诉我!

风速

plt.scatter(weather_df.wind, weather_df.lat)
plt.xlabel("Wind Speed (mph (abs))")
plt.ylabel("Latitude (degrees)")
plt.title("Wind Speed vs. Latitude")
plt.show()
Enter fullscreen mode Exit fullscreen mode

风纬度图

这是一个有趣的图。我们看到的是一片混乱,但在-50度和50度左右有一些明显的峰值。它似乎在两极和赤道附近逐渐下降到零度。一开始我有点困惑,但后来我想起了我八年级的科学课。

全球风流模式

有一股被称为“西风”的风,吹拂在南北纬40度至50度之间。这些风有时也被称为“咆哮西风带”。由于南半球海域广阔(没有陆地或树木阻挡风),它们被用来加快航行速度。在南半球夏季,它们往往会向赤道方向移动,而在冬季,它们则会向极地方向移动。

相反,赤道周围的区域被称为“热带辐合带”,也称为“赤道无风带”。根据季节的不同,这个区域会出现静风和雷暴。

我可以相当肯定地说,我们的数据似乎支持这一趋势。所以,再次为科学欢呼!

包起来

就这样!希望你喜欢这次练习。如果你从我们的数据中发现了其他有趣的发现,请务必与我分享。

祝您愉快!


最初发布于assert_not magic?

文章来源:https://dev.to/rpalo/data-science-cardio-1---weather-20l7
PREV
完成任务——程序员效率指南
NEXT
Bash 中的高级参数处理