Python Programming – 米诺的后花园 https://minuo.org 书写一段蜂花成蜜的往事…… Wed, 22 May 2024 16:02:53 +0000 zh-Hans hourly 1 https://wordpress.org/?v=6.5.8 https://minuo.org/wp-content/uploads/2024/05/minuo-icon-150x150.png Python Programming – 米诺的后花园 https://minuo.org 32 32 使用re库获取阿里云盘分享链接里的标题,链接和提取码 https://minuo.org/get-title-link-and-mention-in-the-shared-link-of-alibaba-cloud-disk-via-re-library https://minuo.org/get-title-link-and-mention-in-the-shared-link-of-alibaba-cloud-disk-via-re-library#respond Sat, 15 Apr 2023 16:10:00 +0000 https://minuo.org/?p=1529 尝试对“大家小书:中国史学入门.azw3 https://www.aliyundrive.com/s/WZWPQVMGLgn 提取码: 0vs5 点击链接保存,或者复制本段内容,打开「阿里云盘」APP ,无需下载极速在线查看,视频原画倍速播放。”这段文字使用正则表达式来获取,名称,链接,和提取码。

]]>
尝试对“大家小书:中国史学入门.azw3 https://www.aliyundrive.com/s/WZWPQVMGLgn 提取码: 0vs5 点击链接保存,或者复制本段内容,打开「阿里云盘」APP ,无需下载极速在线查看,视频原画倍速播放。”这段文字使用正则表达式来获取,名称,链接,和提取码。

使用re库获取阿里云盘分享链接里的标题,链接和提取码:

import re

text = '大家小书:中国史学入门.azw3\nhttps://www.aliyundrive.com/s/WZWPQVMGLgn\n提取码: 0vs5\n点击链接保存,或者复制本段内容,打开「阿里云盘」APP ,无需下载极速在线查看,视频原画倍速播放。'

# Extract file name
file_name_pattern = r'^([^:]+)'
file_name = re.findall(file_name_pattern, text, re.MULTILINE)[0].strip()

# Extract link
link_pattern = r'^https?://(?:www\.)?aliyundrive\.com/s/[A-Za-z0-9]+'
link = re.findall(link_pattern, text, re.MULTILINE)[0]

# Extract extraction code
extract_code_pattern = r'^提取码: ([A-Za-z0-9]+)'
extract_code = re.findall(extract_code_pattern, text, re.MULTILINE)[0]

# Print results
print(f"File name: {file_name}")
print(f"Link: {link}")
print(f"Extraction code: {extract_code}")

输出结果:

File name: 大家小书:中国史学入门.azw3
Link: https://www.aliyundrive.com/s/WZWPQVMGLgn
Extraction code: 0vs5
]]>
https://minuo.org/get-title-link-and-mention-in-the-shared-link-of-alibaba-cloud-disk-via-re-library/feed 0
使用 Flask 的工厂函数构建网站应用 https://minuo.org/build-webapps-using-flask-factory-functions https://minuo.org/build-webapps-using-flask-factory-functions#respond Wed, 29 Mar 2023 16:09:38 +0000 https://minuo.org/?p=1492 重要提示:本示例中的代码并不能运行,仅仅是作为概念性的探讨。

]]>
重要提示:本示例中的代码并不能运行,仅仅是作为概念性的探讨。

一个使用Flask工厂函数构建网站应用的简单示例:

from flask import Flask, render_template

def create_app():
    app = Flask(__name__)

    @app.route('/')
    def index():
        return render_template('index.html')

    return app

if __name__ == '__main__':
    app = create_app()
    app.run()

在这个示例中,我们定义了一个create_app函数,用于创建一个Flask应用。在这个函数中,我们创建了一个Flask对象,并定义了一个路由'/'。这个路由的处理函数是index,它返回一个模板index.html。最后,我们返回这个Flask应用对象。

在最后几行,我们首先调用create_app函数创建了一个Flask应用对象,并通过app.run()方法运行这个应用。如果直接运行这个Python脚本,那么这个应用将会在默认端口(5000)上启动。

这个示例只是一个简单的例子,您可以根据自己的需求进行修改和扩展。希望对您有所帮助!

让我们为这个例子生成一个相对合理的目录结构:

为了更好地组织Flask应用的代码,我们可以将它们分成多个模块,并使用蓝图(Blueprint)将这些模块组装起来。

myapp/
├── __init__.py
├── main/
│   ├── __init__.py
│   ├── routes.py
│   └── templates/
│       └── index.html
├── auth/
│   ├── __init__.py
│   ├── routes.py
│   └── templates/
│       ├── login.html
│       └── register.html
└── static/
    ├── css/
    │   └── style.css
    ├── js/
    │   └── script.js
    └── img/
        └── logo.png

在这个目录结构中,我们将应用的主要代码和模板放在main目录下,将认证相关的代码和模板放在auth目录下,将静态文件(如样式表、脚本、图片等)放在static目录下。

myapp/__init__.py文件可以定义一个create_app函数,用于创建Flask应用,并将蓝图注册到应用上。

main/routes.py文件定义了应用的路由,例如我们之前的示例中的'/'路由。auth/routes.py文件也类似,定义了认证相关的路由。

templates目录下放置HTML模板文件。我们可以按照不同的功能模块,将模板文件分别放置在相应的目录下,以便更好地组织和管理。

static目录下放置应用的静态文件,例如样式表、脚本、图片等。

这只是一个建议的目录结构,您可以根据自己的需求进行修改和扩展。

让我们通过macos终端创建这些目录和文件:

mkdir myapp
cd myapp
touch __init__.py
mkdir main
cd main
touch __init__.py routes.py
mkdir templates
cd templates
touch index.html
cd ../..
mkdir auth
cd auth
touch __init__.py routes.py
mkdir templates
cd templates
touch login.html register.html
cd ../..
mkdir static
cd static
mkdir css js img
cd css
touch style.css
cd ../js
touch script.js
cd ../img
touch logo.png

这些命令将在当前目录下创建一个名为myapp的目录,并在其中创建__init__.py文件以及mainauthstatic子目录。在mainauth子目录中,分别创建__init__.pyroutes.py文件,以及在templates子目录中创建index.htmllogin.htmlregister.html文件。在static子目录中,分别创建cssjsimg子目录,并在其中创建style.cssscript.jslogo.png文件。

您可以按照自己的需求进行修改和扩展。

我们试着把这段程序和目录整合在一起

myapp/__init__.py文件:

from flask import Flask

def create_app():
    app = Flask(__name__)

    # 注册蓝图
    from .main.routes import main
    from .auth.routes import auth
    app.register_blueprint(main)
    app.register_blueprint(auth)

    return app

myapp/main/routes.py文件:

from flask import Blueprint, render_template

main = Blueprint('main', __name__)

@main.route('/')
def index():
    return render_template('index.html')

myapp/main/templates/index.html文件:

<!DOCTYPE html>
<html>
<head>
    <title>MyApp</title>
    <link rel="stylesheet" href="proxy.php?url={{ url_for('static', filename='css/style.css') }}">
</head>
<body>
    <h1>Hello, world!</h1>
    <script src="proxy.php?url={{ url_for('static', filename='js/script.js') }}"></script>
</body>
</html>

myapp/auth/routes.py文件:

from flask import Blueprint, render_template

auth = Blueprint('auth', __name__)

@auth.route('/login')
def login():
    return render_template('login.html')

@auth.route('/register')
def register():
    return render_template('register.html')

myapp/auth/templates/login.html文件:

<!DOCTYPE html>
<html>
<head>
    <title>Login - MyApp</title>
    <link rel="stylesheet" href="proxy.php?url={{ url_for('static', filename='css/style.css') }}">
</head>
<body>
    <h1>Login</h1>
    <form method="post">
        <label for="username">Username:</label>
        <input type="text" name="username" id="username">
        <br>
        <label for="password">Password:</label>
        <input type="password" name="password" id="password">
        <br>
        <input type="submit" value="Login">
    </form>
    <a href="proxy.php?url={{ url_for('auth.register') }}">Register</a>
    <script src="proxy.php?url={{ url_for('static', filename='js/script.js') }}"></script>
</body>
</html>

myapp/auth/templates/register.html文件:

<!DOCTYPE html>
<html>
<head>
    <title>Register - MyApp</title>
    <link rel="stylesheet" href="proxy.php?url={{ url_for('static', filename='css/style.css') }}">
</head>
<body>
    <h1>Register</h1>
    <form method="post">
        <label for="username">Username:</label>
        <input type="text" name="username" id="username">
        <br>
        <label for="password">Password:</label>
        <input type="password" name="password" id="password">
        <br>
        <label for="confirm_password">Confirm Password:</label>
        <input type="password" name="confirm_password" id="confirm_password">
        <br>
        <input type="submit" value="Register">
    </form>
    <a href="proxy.php?url={{ url_for('auth.login') }}">Login</a>
    <script src="proxy.php?url={{ url_for('static', filename='js/script.js') }}"></script>
</body>
</html>

myapp/static/css/style.css文件:

body {
    font-family: Arial, sans-serif;
}

h1 {
    color: #333;
}

form {
    margin: 20px;
}

input[type="text"],
input[type="password"] {
    padding: 10px;
    border-radius: 5px;
    border: 1px solid #ccc;
    margin-bottom: 10px;
}

input[type="submit"] {
    background-color: #4CAF50;
    color: white;
    padding: 10px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}

input[type="submit"]:hover {
    background-color: #45a049;
}

a {
    color: #4CAF50;
    text-decoration: none;
}

a:hover {
    color: #45a049;
}

如需运行以上代码还需做一些局部的调整。比如模板引用,目录结构,url_for的参数等。试着排除这些错误吧。

如果您已经在本地对示例代码进行了优化和改进,那么非常棒!如果您有任何特定的问题或需要进一步的建议,可以提出来让我帮助您。同时,我也可以给您一些常见的建议:

  1. 将应用程序的配置参数保存在单独的配置文件中,并使用app.config.from_object()加载。这样可以方便地管理不同环境(例如开发、测试、生产)中的配置参数。
  2. 使用Blueprint来组织应用程序的路由和视图函数。这样可以使代码更加模块化和可重用,并提高应用程序的可维护性。
  3. 在模板中使用模板继承来避免重复的HTML代码。模板继承使得模板可以继承另一个模板的部分或全部内容,从而避免重复编写相同的HTML代码。
  4. 使用数据库管理系统来存储和检索数据。Flask提供了许多流行的数据库管理系统的扩展,如SQLAlchemy和MongoEngine,使得在应用程序中集成数据库管理系统变得更加容易。
  5. 为了提高应用程序的安全性,应该始终验证用户的输入,例如使用Werkzeug提供的安全的表单处理函数secure_filename(),以避免受到跨站点脚本攻击(XSS)等安全问题的影响。
  6. 最后,对于生产环境,应该使用一个可靠的Web服务器,例如Nginx或Apache,来部署和运行Flask应用程序,并使用Gunicorn或uWSGI等WSGI服务器将其连接到Web服务器。这样可以提高应用程序的性能和可靠性。

希望这些建议能对您有所帮助!

]]>
https://minuo.org/build-webapps-using-flask-factory-functions/feed 0
Flask 网站开发最佳实践 https://minuo.org/flask-web-development-best-practices https://minuo.org/flask-web-development-best-practices#respond Tue, 28 Mar 2023 16:40:33 +0000 https://minuo.org/?p=1479 良好的代码组织和规范,以及使用 Flask 扩展和工具箱,可以使开发更加高效、易于维护和可扩展。以下是 使用Flask 开发时的一些最佳实践:

]]>
良好的代码组织和规范,以及使用 Flask 扩展和工具箱,可以使开发更加高效、易于维护和可扩展。以下是 使用Flask 开发时的一些最佳实践:

  1. 使用工厂函数:将 Flask 应用程序的创建与配置分离可以使代码更加清晰,易于维护,并且可以在不同的环境中重用应用程序。
  2. 将配置信息放在单独的文件中:将配置信息放在单独的 Python 模块或 JSON 文件中,可以使您更轻松地管理配置信息,并在不同的环境中共享配置。
  3. 使用蓝图:使用蓝图可以将应用程序分解为更小的模块,这使得应用程序更易于维护,并且可以使多人协作更加容易。
  4. 使用 ORM:使用 ORM 可以使您更轻松地与数据库交互,并将数据库访问逻辑与应用程序代码分离。
  5. 错误处理:对于常见的错误类型(例如 404、500 等),您应该提供自定义的错误页面或 API 响应,这可以提高应用程序的用户体验。
  6. 使用模板继承:使用模板继承可以使您更轻松地维护应用程序的视图和模板,并避免重复代码。
  7. 使用表单验证:使用表单验证可以提高应用程序的安全性,并帮助您捕获用户输入错误。
  8. 使用 Flask-WTF:使用 Flask-WTF 可以使表单验证更加简单,并提供内置的 CSRF 保护。
  9. 使用 Flask-Login:使用 Flask-Login 可以轻松地处理用户认证和授权,并提供内置的记住我功能。
  10. 使用 Flask-Caching:使用 Flask-Caching 可以轻松地缓存视图和其他数据,并提高应用程序的性能。

总之,良好的代码组织和规范,以及使用 Flask 扩展和工具箱,可以使开发更加高效、易于维护和可扩展。

Flask 开发中常用的一些插件

  1. Flask-WTF:提供表单验证和 CSRF 保护等功能。
  2. Flask-SQLAlchemy:提供与关系型数据库的交互功能,并使用 ORM 简化数据库访问逻辑。
  3. Flask-Migrate:用于数据库迁移,使数据库模式的变更更加安全和简单。
  4. Flask-Login:用于处理用户认证和授权,提供用户管理和登录状态管理等功能。
  5. Flask-Cache:用于缓存页面和其他数据,提高应用程序的性能。
  6. Flask-Mail:用于发送电子邮件,提供邮件发送功能并支持 HTML 格式的邮件内容。
  7. Flask-RESTful:用于构建 RESTful API,提供基于类的视图和请求解析等功能。
  8. Flask-Admin:用于创建 Web 管理界面,可以快速创建 CRUD 页面和自定义视图。
  9. Flask-Uploads:用于处理文件上传,提供文件上传和存储功能,并支持不同的存储后端。
  10. Flask-Security:用于增强 Web 应用程序的安全性,提供用户身份验证、密码哈希、角色和权限等功能。

这些插件可以帮助 Flask 开发人员快速实现常见的功能,提高开发效率和代码质量。

]]>
https://minuo.org/flask-web-development-best-practices/feed 0
如何解决opencv-python 安装缓慢问题 https://minuo.org/how-to-solve-the-slow-installation-of-opencv-through-pip https://minuo.org/how-to-solve-the-slow-installation-of-opencv-through-pip#respond Mon, 30 Jan 2023 08:55:11 +0000 https://minuo.org/?p=1416 在搭建开发环境的过程中,直接利用pip 安装,经常会出现下载缓慢或者time out的情况。

]]>
在搭建开发环境的过程中,直接利用pip 安装,经常会出现下载缓慢或者time out的情况。

pip install opencv-python

所以采用镜像安装,这里也可以指定其他源。

pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple
]]>
https://minuo.org/how-to-solve-the-slow-installation-of-opencv-through-pip/feed 0
Python协程 & 异步编程(asyncio) 笔记 https://minuo.org/python-coroutine-asyncio-await https://minuo.org/python-coroutine-asyncio-await#respond Sun, 02 Oct 2022 16:27:38 +0000 https://minuo.org/?p=1233 最近看到别人代码里面有这种方式,在编码下载歌词的时候想试试,查了资料,把样例放这里吧,理解了皮毛,照猫画虎。

]]>
最近看到别人代码里面有这种方式,在编码下载歌词的时候想试试,查了资料,把样例放这里吧,理解了皮毛,照猫画虎。

1.协程的实现

协程(Coroutine),也可以被称为微线程,是一种用户态内的上下文切换技术。简而言之,其实就是通过一个线程实现代码块相互切换执行。例如:

def func1():
    print(1)
    ...   # 协程介入
    print(2)
    
def func2():
    print(3)
    ...   # 协程介入
    print(4)

func1()
func2()

上述代码是普通的函数定义和执行,按流程分别执行两个函数中的代码,并先后会输出:1、2、3、4。但如果介入协程技术那么就可以实现函数见代码切换执行,最终输入:1、3、2、4 。

在Python3.4+可以通过以下:

  • asyncio,在Python3.4中引入的模块用于编写协程代码;
  • async & awiat,在Python3.5中引入的两个关键字,结合asyncio模块可以更方便的编写协程代码。

通过标准库的方式实现:asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。

import asyncio

@asyncio.coroutine
def func1():
    print(1)
    yield from asyncio.sleep(2)  # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(2)

@asyncio.coroutine
def func2():
    print(3)
    yield from asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(4)

tasks = [
    asyncio.ensure_future( func1() ),
    asyncio.ensure_future( func2() )
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

通过关键字实现方法

async & await 关键字在Python3.5版本中正式引入,代替了asyncio.coroutine 装饰器,基于他编写的协程代码其实就是上一示例的加强版,让代码可以更加简便可读。

import asyncio

async def func1():
    print(1)
    await asyncio.sleep(2)  # 耗时操作  
    print(2)

async def func2():
    print(3)
    await asyncio.sleep(2)   # 耗时操作
    print(4)

tasks = [
    asyncio.ensure_future(func1()),
    asyncio.ensure_future(func2())
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

2.案例:

2.1 同步实现案例

# requests库仅支持同步的http网络请求
import requests

def download_image(url):
  print("开始下载:",url)
  # 发送网络请求,下载图片
  response = requests.get(url)
  # 图片保存到本地文件
  file_name = url.rsplit('_')[-1]
  with open(file_name, mode='wb') as file_object:
      file_object.write(response.content)
print("下载完成")


if __name__ == '__main__':
  url_list = [
      'https://www.1.jpg',
      'https://www.2.jpg',
      'https://www.3.jpg'
  ]
  for item in url_list:
      download_image(item)

2.2 基于协程的实现

# aiohttp 为支持异步编程的http请求库
import aiohttp
import asyncio

async def fetch(session, url):
  print("发送请求:", url)
  async with session.get(url, verify_ssl=False) as response:
      content = await response.content.read()
      file_name = url.rsplit('_')[-1]
      with open(file_name, mode='wb') as file_object:
          file_object.write(content)

async def main():
  async with aiohttp.ClientSession() as session:
      url_list = [
          'https://www.1.jpg',
          'https://www.2.jpg',
          'https://www.3.jpg'
      ]
      tasks = [asyncio.create_task(fetch(session, url)) for url in url_list]
      await asyncio.wait(tasks)


if __name__ == '__main__':
  asyncio.run(main())

2.3 下载歌词的部分代码

# aiohttp 为支持异步编程的http请求库
import aiohttp
import asyncio

async def fetch(session, song):
    song_id = song['id']
    song_name = song['name']
    url = get_song_lyrics_url(song_id)
    
    print("开始下载:", song_id, song_name, url)
    async with session.get(url, verify_ssl=False) as response:
        content = await response.content.read()
        
        json_path = f_path + str(song_id) + "-" + song_name + '.json'
        with open(json_path, mode='wb') as file_object:
            file_object.write(content)
    print(song_name, "下载完成:")
            
async def main():
    async with aiohttp.ClientSession() as session:
        tasks = [asyncio.create_task(fetch(session, song)) for song in songs]    
        await asyncio.wait(tasks)
        
await main()
# if __name__ == '__main__':
#     asyncio.run(main())
]]>
https://minuo.org/python-coroutine-asyncio-await/feed 0
用Python进行时间序列预测-对未来的预测 https://minuo.org/time-series-forecasting-in-python-chapter-2-a-naive-prediction-for-future https://minuo.org/time-series-forecasting-in-python-chapter-2-a-naive-prediction-for-future#respond Thu, 08 Sep 2022 19:10:00 +0000 https://minuo.org/?p=1194

本章内容
定义一个基线模型
使用平均值设置基线
使用前一个时间窗口的平均值构建基线
使用前一个时间戳创建基线
实现朴素的季节性预测

]]>

本章内容
定义一个基线模型
使用平均值设置基线
使用前一个时间窗口的平均值构建基线
使用前一个时间戳创建基线
实现朴素的季节性预测

在第 1 章中,我们介绍了什么是时间序列以及预测时间序列与传统回归任务的不同之处。还了解了构建成功预测项目的必要步骤,从定义目标到构建模型、部署模型以及在收集到新数据时对其进行更新。现在我们已经准备好开始预测时间序列之旅了。

您将首先学习如何对未来做出天真的预测,这将作为基准。基线模型是一个简单的解决方案,它使用启发式或简单的统计数据来计算预测。开发基线模型并不总是一门精确的科学。我们将通过可视化数据和检测那些可能用于进行预测的模式来获得一些直觉。在任何建模项目中,有一个基准很重要,因为您可以使用它来比较您将在未来构建的更复杂模型的性能。 知道模型好坏或性能好的唯一方法是将其与基线进行比较。

在本章中,假设我们希望预测强生公司的季度每股收益 (EPS)。我们可以查看图 2.1 中的数据集,它与您在第 1 章中看到的相同。具体来说,我们将使用 1960 年至 1979 年底的数据来预测 1980 年四个季度的 EPS。预测期如图 2.1 中的灰色区域所示。

图 2.1 强生公司从 1960 年到 1980 年以美元 (USD) 表示的季度每股收益, 1980 年(如灰色区域所示)

您可以在图 2.1 中看到我们的数据有一个趋势,因为它随着时间的推移而增加。此外,还存在季节性模式,因为在一年或四个季度的过程中,我们可以反复观察高峰和低谷。这意味着存在季节性。

回想一下,当我们在第 1 章分解时间序列时,我们已经确定了这些组件中的每一个。组件如图 2.2 所示。 我们将在本章后面详细研究其中一些组件,因为它们将帮助我们获得对数据行为的一些直觉,进而帮助我们开发一个好的基线模型。

我们将首先定义什么是基线模型,然后我们将开发四个不同的基线来预测强生公司的季度每股收益。 现在是我们最终开始使用 Python 和进行时间序列预测的时候了。

图 2.2 强生公司 1960 年至 1980 年季度收益分解

2.1 定义基线模型

基线模型是解决我们问题的一个简单方案。它通常使用启发式或简单的统计数据来生成预测。 基线模型是您能想到的最简单的解决方案——它不需要任何培训,并且实施成本应该非常低。

你能想出我们项目的基线吗?知道我们要预测强生公司的每股收益,你能做出的最基本、最简单的预测是什么?

在时间序列的上下文中,我们可以用来构建基线的一个简单统计数据是算术平均值。 我们可以简单地计算一段时间内的值的平均值,并假设未来的值将等于该平均值。 在预测强生公司每股收益的背景下,这就像说:1960 年至 1979 年间的平均每股收益为 4.31 美元。 因此,我预计 1980 年接下来四个季度的每股收益将等于每季度 4.31 美元。

另一个可能的基线是天真地预测最后记录的数据点。 在我们的上下文中,这就像说:如果本季度的每股收益为 0.71 美元,那么下一季度的每股收益也将为 0.71 美元。

或者,如果我们在数据中看到周期性模式,我们可以简单地将这种模式重复到未来。 在当前的背景下,这就像说:如果 1979 年第一季度的每股收益为 14.04 美元,那么 1980 年第一季度的每股收益也将是 14.04 美元。

可以看到这三个可能的基线依赖于我们数据集中观察到的简单统计数据、启发式方法和模式。

基线模型

基线模型是预测问题的简单解决方案。 它依赖于启发式或简单的统计数据,通常是最简单的解决方案。 它不需要模型拟合,并且很容易实现。

您可能想知道这些基线模型是否有用。这些简单的方法能在多大程度上预测未来?我们可以通过预测 1980 年并根据 1980 年的观测数据检验我们的预测来回答这个问题。这称为样本外预测,因为我们正在对模型开发时未考虑的时期进行预测。通过这种方式,我们可以衡量模型的性能,并了解当我们预测超出现有数据(在本例中是 1981 年及以后的数据)时它们的表现如何。

在接下来的部分中,您将学习如何开发此处提到的不同基线来预测强生公司的季度每股收益。

2.2 预测历史平均值

如本章开头所述,我们将使用强生公司从 1960 年到 1980 年以美元 (USD) 表示的季度每股收益。我们的目标是使用 1960 年到 1979 年底的数据来预测 1980 年的四个季度。我们将讨论的第一个基线使用历史平均值,即过去值的算术平均值。它的实现很简单:计算训练集的平均值,这将是我们对 1980 年四个季度的预测。不过,首先,我们需要做一些我们将在所有基线实现中使用的初步工作。

2.2.1 基线实现的基本设置

我们的第一步是加载数据集。 为此,我们将使用 pandas 库并使用 read_csv 方法将数据集加载到 DataFrame 中。 您可以在本地计算机上下载文件并将文件的路径传递给 read_csv 方法,或者只需输入 CSV 文件托管在 GitHub 上的 URL。在本案例中,我们将使用该文件:

import pandas as pd
df = pd.read_csv('../data/jj.csv')

注意:本章的全部代码可以在 GitHub 上找到:https://github.com/marcopeix/TimeSeriesForecastingInPython/tree/master/CH02

DataFrame 是 pandas 中最常用的数据结构。 它是一种二维标记数据结构,其列可以保存不同类型的数据,例如字符串、整数、浮点数或日期。

我们的第二步是将数据拆分为用于训练的训练集和用于测试的测试集。鉴于我们的范围是 1 年,我们的训练集将从 1960 年开始,一直到 1979 年底。为我们的测试集保存 1980 年收集的数据。您可以将 DataFrame 视为具有列名和行索引的表格或电子表格。

要查看 DataFrame 中的数据集,我们可以通过运行以下代码显示前五个条目:

df.head()

将得到如图2.3所示的输出

图 2.3 将帮助您更好地了解我们的 DataFrame 保存的数据类型。其中有日期列,它指定每个季度末计算 EPS 时间。数据列保存了以美元 (USD) 为单位的 EPS 值。

我们可以选择显示数据集的最后五个条目并获得图 2.4 中的输出:

df.tail()
图 2.4 我们数据集的最后五个条目

在这里,我们可以看到 1980 年的四个季度,我们将尝试使用不同的基线模型进行预测。 我们会将我们的预测与 1980 年的观测数据进行比较,以评估每个基线的表现。

在图 2.4 中,我们看到了 1980 年的四个季度,这是我们将尝试使用我们的基线模型来预测的。 我们将通过将我们的预测与 1980 年四个季度的数据列中的值进行比较来评估我们的基线表现。我们的预测越接近观察值越好。

开发基线模型之前的最后一步是将数据集拆分为训练集和测试集。 如前所述,训练集将包含 1960 年至 1979 年底的数据,而测试集将包含 1980 年的四个季度。训练集将是我们用于开发模型的唯一信息。 建立模型后,我们将预测接下来的四个时间步长,这将对应于我们测试集中 1980 年的四个季度。 这样,我们可以将我们的预测与观察到的数据进行比较,并评估我们基线的性能。

为了进行拆分,我们将指定我们的训练集将包含df中保存的所有数据,除了最后4个条目。测试集将仅由最后4个条目组成。 这是下一个代码块的作用:

train = df[:-4]
test = df[-4:]

2.2.2 实现历史平均基线

现在我们准备好实现我们的基线了。首先使用整个训练集的算术平均值。 为了计算平均值,我们将使用 numpy 库,因为它是一个非常快速的 Python 科学计算包,与 DataFrame 配合得非常好:

import numpy as np
historical_mean = np.mean(train['data']) # 计算训练集中数据列的算术平均值。
print(historical_mean)

在前面的代码块中,我们首先导入 numpy 库,然后计算整个训练集的 EPS 平均值并将其打印在屏幕上。输出结果为 4.31 美元。 这意味着从 1960 年到 1979 年底,强生公司的季度每股收益平均为 4.31 美元。

现在我们将天真地预测 1980 年每个季度的这个值。为此,我们将简单地创建一个新列 pred_mean,它将训练集的历史平均值作为预测:

test.loc[:, 'pred_mean'] = historical_mean # 把历史数据的平均值设置为预测值

接下来,我们需要定义和计算一个误差指标,以评估我们在测试集上的预测性能。 在这里我们将使用平均绝对百分比误差 (MAPE,Mean Absolute Percentage Error)。这意味着无论我们使用两位数还是六位数,MAPE 将始终以百分比表示。因此,MAPE 返回预测值平均偏离观测值或实际值的百分比,无论预测值高于还是低于观测值。 MAPE 在方程 2.1 中定义。

公式2.1

MAPE = \frac{1}{n} \sum_{i=1}^n | \frac{A_{i}-F_{i}}{A_{i}} | *100MAPE=n1​i=1∑n​∣AiAi​−Fi​​∣∗100

公式2.1中,*Ai*为时间点i的实际值,*Fi*为时间点的预测值; n是预测的数量。本例中我们预测的是 1980 年的4个季度,所以 n = 4。在求和中,从实际值中减去预测值,然后将结果除以实际值,得到百分比误差;然后取百分比误差的绝对值。对 n 个时间点中的每一个重复此操作,并将结果相加。 最后,我们将总和除以时间点数 n,最终计算出了平均绝对百分比误差。

下面,我们在 Python 中实现这个函数。定义一个 mape 函数,它接受两个向量:y_true 表示在测试集中观察到的实际值,y_pred 表示预测值。本例中,我们使用Numpy数组,不需要循环对所有值求和。可以简单地从 y_true 数组中减去 y_pred 数组并除以 y_true 以获得百分比误差。 然后取其绝对值,==再取其结果的平均值,这将负责对向量中的每个值求和并除以预测的数量。(After that, we take the mean of the result, which will take care of summing up each value in the vector and dividing by the number of predictions. )==最后,我们将结果乘以 100,让输出以百分比而不是十进制数表示:

def mape(y_true, y_pred):
    return np.mean(np.abs((y_true-y_pred) / y_true))*100

现在我们可以计算基线的 MAPE。 我们的实际值在 test 的data列中,因此它将是传递给 mape 函数的第1个参数。 我们的预测在 test 的 pred_mean 列中,所以它将是函数的第2个参数:

mape_hist_mean = mape(test['data'],test['pred_mean'])
print(mape_hist_mean)

运行该函数给出 70.00% 的 MAPE。 这意味着我们的基线与 1980 年观察到的强生公司季度每股收益平均偏离 70%。

接下来,让我们可视化我们的预测结果,以更好地了解我们 70% 的 MAPE。

Listing 2.1 Visualizing our forecasts

import matplotlib.pyplot as plt
fig, ax = plt.subplots()

ax.plot(train['date'],train['data'], 'g-.', label='Train')
ax.plot(test['date'],test['data'], 'b-.', label='Test')
ax.plot(test['date'],test['pred_mean'], 'r--', label='Predicted')

ax.set_xlabel('Date')
ax.set_ylabel('Earnings per share (USD)')
ax.axvspan(80,83,color='#808080',alpha=0.2)
ax.legend(loc=2)

plt.xticks(np.arange(0, 85, 8), [1960, 1962, 1964, 1966, 1968, 1970, 1972, 1974, 1976, 1978, 1980])

fig.autofmt_xdate()
plt.tight_layout()

在清单 2.1 中,我们使用 matplotlib 库(它是 Python 中最流行的用于生成可视化的库)生成一个图表,显示训练数据、预测范围、测试集的观察值以及 1980 年每个季度的预测。

首先,我们初始化一个figure和一个 ax 对象。 一个图形可以包含许多 ax 对象,这允许我们创建一个包含两个、三个或更多绘图的图形。 在这种情况下,我们正在创建一个带有单个图的图形,因此我们只需要一个 ax

其次,我们将数据绘制在 ax 对象上。我们使用绿色虚线和点划线绘制train数据,并给这条曲线一个标签“Train”。该标签稍后可用于生成图表的图例。然后我们绘制test数据并使用带有“Test”标签的蓝色连续曲线。最后,我们使用带有“Predicted”标签的红色虚线绘制了我们的预测值曲线。

第三,通过标记x 轴和 y 轴绘制一个矩形区域来说明预测范围。因为我们的预测范围是 1980 年的四个季度,所以该区域应该从索引 80 开始,到索引 83 结束,跨越 1980 年全年。请记住,我们通过运行 df.tail() 获得了 1980 年最后一个季度的指数,结果如图 2.5所示。

图 2.5 数据集的最后五个条目

我们将这个区域设备为灰色,并使用 alpha 参数指定不透明度。 当 alpha 为 1 时,形状完全不透明; 当 alpha 为 0 时,它是完全透明的。 在我们的例子中,将使用 20% 或 0.2 的不透明度。

然后我们为 x 轴上的刻度指定标签。默认情况下,标签将显示数据集每个季度的数据,这将创建一个非常拥挤且难易辨识的 x 轴标签。相反,我们将每 2 年显示一次年份。为此,我们将生成一个数组,指定标签必须出现的索引。这就是 np.arange(0, 81, 8) 的作用:它生成一个从 0 开始,到 80 结束的数组,因为不包括结束索引 (81),步长为 8,因为 2 年内有 8 个季度。这将有效地生成以下数组:[0,8,16,...72,80]。然后指定一个包含每个索引处标签的数组,因此它必须以 1960 开始并以 1980 结束,就像我们的数据集一样。

最后,我们使用 fig.automft_xdate() 来自动格式化 x 轴上的刻度标签。 它会稍微旋转它们并确保它们清晰易读。 最后使用 plt.tight_layout() 删除图形周围的任何多余空白。

最终结果如图 2.6。 显然,这个基线没有产生准确的预测,因为预测线离测试线很远。现在我们知道,我们的预测平均比 1980 年每个季度的实际每股收益低 70%。尽管 1980 年的每股收益一直高于 10 美元,但我们预测每个季度仅为 4.31 美元。

图 2.6 预测历史平均值作为基线。 可以看到预测值与测试集中的实际值相差甚远。 该基线给出了 70% 的 MAPE。

不过,我们能从中学到什么? 查看我们的训练集,我们可以看到一个积极的趋势,因为 EPS 随着时间的推移而增加。 来自我们数据集分解的趋势分量进一步支持了这一点,如图 2.7 所示。

正如你所看到的,我们不仅有趋势,而且趋势在 1960 年到 1980 年之间并不是恒定的——它变得越来越陡峭。 因此,1960 年观察到的 EPS 可能无法预测 1980 年的 EPS,因为我们有一个积极的趋势,EPS 值随着时间的推移而增加,并且以更快的速度增加。

图 2.7 我们的时间序列的趋势部分。 您可以看到我们的数据呈积极趋势,因为它会随着时间的推移而增加。

你能改善我们的基线吗?
在继续下一部分之前,你能想出一种方法来改进我们的基线,同时仍然使用平均值吗? 你认为取更短和更近的时间段的平均值会有所帮助吗(例如,从 1970 年到 1979 年)?

2.3 预测去年的平均值

从之前的基线中吸取的教训是,由于我们数据集中的积极趋势成分,早期的值似乎不能预测未来长期的值。早期的值似乎太小,无法代表 EPS 在 1979 年底和 1980 年达到的新水平。

如果我们在训练集中使用去年的平均值来预测下一年呢? 这意味着我们将计算 1979 年的平均每股收益,并预测 1980 年每个季度的每股收益——随着时间的推移而增加的最近的值可能更接近 1980 年观察到的值。目前,这只是一个假设, 所以让我们实现这个基线并测试它,看看它是如何执行的。

我们的数据已经分为测试集和训练集(在第 2.2.1 节中完成),因此我们可以继续计算训练集中最后一年的平均值,对应于 1979 年的最后四个数据点:

last_year_mean = np.mean(train[-4:]) # 计算1979年四个季度的平均 EPS,它们是训练集的最后四个数据点。
print(last_year_mean)

通过计算得出平均每股收益为 12.96 美元。 因此,我们将预测强生公司在 1980 年四个季度的每股收益为 12.96 美元。使用我们用于上一个基线的相同程序,我们将创建一个新的 pred_last_yr_mean 列来保存去年的平均值 我们的预测:

test.loc[:, 'pred_last_yr_mean'] = last_year_mean

然后,使用我们之前定义的 mape 函数,可以评估新基线的性能。请记住,第一个参数是观察值,保存在测试集中。 然后我们传入预测值,它们pred_last_yr_mean 列:

mape_last_year_mean = mape(test['data'], test['pred__last_yr_mean'])
print(mape_last_year_mean)

我们得到了 15.60% 的 MAPE。 我们可以在图 2.8 中可视化我们的预测。

你能重现图 2.8 吗?
作为练习,尝试重新创建图 2.8,以使用 1979 年各季度的平均值来可视化预测。代码应该与清单 2.1 相同,只是这次预测位于不同的列中。

图 2.8 预测训练集(1979)中上一年的平均值作为基线模型。 可以看到,与我们先前在图 2.6 中构建的基线相比,预测更接近测试集的实际值。

这个新基线比之前的基线有了明显的改进,尽管它的实现同样简单,因为我们将 MAPE 从 70% 降低到 15.6%。这意味着我们的预测平均偏离观测值 15.6%。 使用去年的平均值是朝着正确方向迈出的一大步。我们希望 MAPE 尽可能接近 0%,因为这将转化为更接近我们预测范围内实际值的预测。

我们可以从这个基线中了解到,未来的价值可能取决于历史上不太远的过去价值。这里涉及到了自相关的内容,我们将在第 5 章深入探讨这个主题。现在,让我们看看我们可以针对这种情况开发的另一个基线。

2.4 使用最后一个已知值进行预测

之前,我们使用不同时期的平均值来开发基线模型。 到目前为止,最好的基线是我们训练集中最后记录年份的平均值,因为它产生了最低的 MAPE。 我们从该基线中了解到,未来的价值取决于过去的价值,而不是那些太远的时间。事实上,预测 1960 年至 1979 年的平均每股收益比预测 1979 年的平均每股收益更差。

因此,我们可以假设使用训练集的最后一个已知值作为基线模型会给我们更好的预测,这将转化为接近 0% 的 MAPE。 让我们检验这个假设。

第一步是提取我们训练集的最后一个已知值,它对应于 1979 年最后一个季度记录的 EPS:

last = train.data.iloc[-1]
print(last) # 9.99

当我们检索 1979 年最后一个季度记录的 EPS 时,我们得到 9.99 美元的值。 因此,我们将预测强生公司 1980 年四个季度的每股收益为 9.99 美元。

同样,我们将追加一个名为 pred_last 的新列来保存预测。

test.loc[:, 'pred_last'] = last

然后,使用我们之前定义的相同 MAPE 函数,我们可以评估这个新基线模型的性能。 同样,我们将来自测试集的实际值和来自测试 pred_last 列的预测传递给函数:

mape_last = mape(test['data'], test['pred_last'])
print(mape_last)

这给了我们 30.45% 的 MAPE。 我们可以将图 2.9 中的预测可视化。

你能重现图 2.9 吗?
尝试自己制作图 2.9! 作为数据科学家,对我们来说,以不在我们领域工作的人可以访问的方式传达我们的结果非常重要。 因此,制作显示我们预测的图表是一项重要的技能开发。

似乎我们的新假设在我们建立的最后一个基线上没有改进,因为我们的 MAPE 为 30.45%,而我们使用 1979 年的平均 EPS 实现了 15.60% 的 MAPE。因此,这些新的预测与 1980 年的观测值相去甚远。

图 2.9 预测训练集的最后一个已知值作为基线模型。 我们可以看到,MAPE 为 30.45% 的这条基线优于我们的第一个基线,但性能低于我们的第二个基线。

这可以通过 EPS 表现出周期性行为来解释,在前三个季度它很高,然后在最后一个季度下降。 使用最后一个已知值没有考虑季节性,因此我们需要使用另一种简单的预测技术来看看我们是否可以产生更好的基线。

2.5 实现一个朴素的季节性预测

我们在本章中考虑了前两个基线的趋势分量,但我们没有研究数据集中的另一个重要分量,即图 2.10 所示的季节性分量。 我们的数据中有明显的周期性模式,这是我们可以用来构建最后一个基线的一条信息:朴素的季节性预测。

图 2.10 我们的时间序列的季节性成分。 我们可以在这里看到周期性波动,这表明存在季节性。

朴素的季节性预测采用最后观察到的周期并将其重复到未来。在我们的例子中,一个完整的周期发生在四个季度,因此我们将采用 1979 年第一季度的 EPS 并预测 1980 年第一季度的值。然后我们将采用 1979 年第二季度的 EPS 并预测 1980 年第二季度的值。这个过程将在第三和第四季度重复。

在 Python 中,我们可以通过简单地获取训练集的最后四个值来实现这个基线,它们对应于 1979 年的四个季度,并将它们分配给 1980 年的相应季度。下面的代码附加了 pred_last_season 列来保存 我们从朴素的季节性预测方法中做出的预测:

test.loc[:, 'pred_last_season'] = train['data'][-4:].values # 我们的预测值是我们训练集的最后四个值,对应于 1979 年的季度。

然后我们以与前面部分相同的方式计算 MAPE:

mape_naive_seasonal = mape(test['data'], test['pred_last_season'])
print(mape_naive_seasonal) # 11.56

这给了我们 11.56% 的 MAPE,这是本章所有基线中最低的 MAPE。 图 2.11 说明了我们的预测与测试集中观察到的数据的比较。 作为练习,我强烈建议您尝试自己重新创建它。

图 2.11 在测试集上的朴素季节预测结果

这个预测更类似于在测试集中观察到的数据,它导致了最低的 MAPE。 显然,该数据集的季节性对未来值有影响,在预测时必须加以考虑。

如您所见,我们幼稚的季节性预测导致我们在本章中建立的所有基线中的最低 MAPE。 这意味着季节性对未来值有重大影响,因为将上一个季节重复到未来会产生相当准确的预测。 直观地说,这是有道理的,因为我们可以清楚地观察到图 2.11 中每年都在重复的周期性模式。 当我们为这个问题开发更复杂的预测模型时,必须考虑季节性影响。 我将在第 8 章详细解释如何解释它们。

2.6 下一步

在本章中,我们为我们的预测项目开发了四个不同的基线。 我们使用了整个训练集的算术平均值、训练集中最后一年的平均值、训练集的最后一个已知值以及一个幼稚的季节预测。 然后使用 MAPE 指标在测试集上评估每个基线。 图 2.12 总结了我们在本章中开发的每个基线的 MAPE。 如您所见,使用朴素季节性预测的基线具有最低 MAPE,因此性能最佳。

图 2.12 本章开发的四个基线的 MAPE。 MAPE越低,基线越好; 因此,我们将选择朴素的季节性基线作为我们的基准,并将其与我们更复杂的模型进行比较。

请记住,基线模型可作为比较的基础。我们将通过应用统计学习或深度学习技术开发更复杂的模型,当我们根据测试集评估更复杂的解决方案并记录我们的错误指标时,我们可以将它们与基线进行比较。在我们的例子中,我们会将复杂模型的 MAPE 与我们幼稚的季节性预测的 MAPE 进行比较。如果一个复杂模型的 MAPE 低于 11.56%,那么我们就会知道我们有一个性能更好的模型。

在某些特殊情况下,只能使用幼稚的方法来预测时间序列。这些是过程随机且无法使用统计学习方法预测的特殊情况。这意味着存在随机性——我们将在下一章探讨这一点。

本章概要
时间序列预测从基线模型开始,该模型用作与更复杂模型进行比较的基准。
基线模型是我们预测问题的一个简单的解决方案,因为它只使用启发式或简单的统计数据,例如平均值。
MAPE 代表平均绝对百分比误差,它是预测值与实际值的偏差程度的直观度量。
有很多方法可以制定基线。 在本章中,您了解了如何使用均值、最后一个已知值或上一个季节。

]]>
https://minuo.org/time-series-forecasting-in-python-chapter-2-a-naive-prediction-for-future/feed 0
用Python进行时间序列预测-了解时间序列预测 https://minuo.org/time-series-forecasting-in-python-chapter-1-understanding-time-series-forecasting https://minuo.org/time-series-forecasting-in-python-chapter-1-understanding-time-series-forecasting#respond Wed, 07 Sep 2022 18:55:00 +0000 https://minuo.org/?p=1170 很少有现象不受时间的影响,这本身就足以证明理解什么是时间序列的重要性。 在本书的第一部分,我们将定义时间序列并探索使用它们的特殊性。 我们还将使用简单的方法开发我们的第一个预测模型。 这些将作为基线模型,我们将在本书中重复使用这些技术。 最后,我们将研究无法进行预测的情况,以便我们识别并避免陷入该陷阱。

]]>
很少有现象不受时间的影响,这本身就足以证明理解什么是时间序列的重要性。 在本书的第一部分,我们将定义时间序列并探索使用它们的特殊性。 我们还将使用简单的方法开发我们的第一个预测模型。 这些将作为基线模型,我们将在本书中重复使用这些技术。 最后,我们将研究无法进行预测的情况,以便我们识别并避免陷入该陷阱。

1 了解时间预测序列

本章内容

  • 时间序列简介
  • 了解时间序列三个主要组成部分
  • 成功的预测项目所必需的步骤
  • 预测时间序列与其他回归任务有何不同

时间序列存在于从气象学到金融、计量经济学和市场营销的各个领域。 通过记录和分析数据,我们可以研究时间序列来分析工业流程或跟踪业务指标,例如销售额或参与度。 此外,借助大量可用数据,数据科学家可以将他们的专业知识应用于时间序列预测技术。

您可能遇到过其他关于时间序列的课程、书籍或文章,它们在 R 中实现了他们的解决方案,R 是一种专门用于统计计算的编程语言。 许多预测技术都使用统计模型,您将在第 3 章及以后的章节中学习。因此,我们做了大量工作来开发软件包,以使用 R 无缝进行时间序列分析和预测。然而,大多数数据科学家都需要精通 Python,因为它是机器学习领域中使用最广泛的语言。近年来,社区和大公司开发了强大的库,利用 Python 执行统计计算和机器学习任务、开发网站等等。虽然 Python 远不是一种完美的编程语言,但它的多功能性对其用户来说是一个巨大的好处,因为我们可以开发模型、执行统计测试,并可能通过 API 或开发 Web 界面为我们的模型提供服务,同时使用相同的编程语言。本书将向您展示如何仅使用 Python 实现时间序列预测的统计学习技术和机器学习技术。

本书将完全专注于时间序列预测。您将首先学习如何进行简单的预测,这些预测将作为更复杂模型的基准。然后我们将使用移动平均模型和自回归模型这两种统计学习技术来进行预测。这些将作为我们将要介绍的更复杂的建模技术的基础,这将使我们能够考虑非平稳性、季节性影响和外生变量的影响。之后,我们将从统计学习技术切换到深度学习方法,以预测具有高维的非常大的时间序列,在这种情况下,统计学习的性能通常不如深度学习。

现在,本章将探讨时间序列预测的基本概念。我将从定义时间序列开始,以便您可以识别一个。然后,我们将继续讨论时间序列预测的目的。 最后,您将了解为什么预测时间序列与其他回归问题不同,以及为什么该主题值得拥有一本书。

1.1 时间序列简介

理解和执行时间序列预测的第一步是了解什么是时间序列。简而言之,时间序列就是按时间排序的一组数据点。 此外,数据通常在时间上等间隔,这意味着相等的间隔将每个数据点分开。

简单来说,数据可以每小时或每分钟记录一次,也可以每月或每年平均记录。 一些典型的时间序列示例包括特定股票的收盘价、家庭用电量或室外温度。

时间序列
时间序列是按时间排序的一组数据点。
数据在时间上是等间隔的,这意味着它是在每小时、每分钟、每月或每季度记录的。 时间序列的典型例子包括股票收盘价、家庭用电量或室外温度。

让我们考虑一个数据集,该数据集表示 1960 年至 1980 年强生公司股票的季度每股收益(以美元计),如图 1.1 所示。 我们将在本书中经常使用这个数据集,因为它有许多有趣的特性,可以帮助你学习更复杂的预测问题的高级技术。

如您所见,图 1.1 清楚地表示了一个时间序列。 数据按时间索引,如横轴所示。 此外,数据在时间上是等间隔的,因为它是在每年每个季度末记录的。 我们可以看到数据有一个趋势,因为值随着时间的推移而增加。 我们还看到收益在每年的过程中上下波动,并且这种模式每年都在重复。

图 1.1 强生公司 1960 年至 1980 年的季度收益(以美元计)呈现积极趋势和周期性行为

1.1.1 时间序列的组成部分

我们可以通过查看它们的三个组成部分来进一步理解时间序列:趋势、季节性组成部分和残差。 事实上,所有时间序列都可以分解为这三个元素。

可视化时间序列的组成部分称为分解。 分解被定义为一项统计任务,它将时间序列分成不同的组成部分。 我们可以可视化每个单独的组件,这将帮助我们识别数据中的趋势和季节性模式,仅仅通过查看数据集并不总是那么简单。 

让我们仔细看看强生公司季度每股收益的分解,如图 1.2 所示。您可以看到观察到的数据是如何分成趋势、季节性和残差的。让我们更详细地研究图表的每一部分。

图 1.2 强生公司 1960 年至 1980 年季度收益分解

首先,顶部的图表,标记为 Observed,简单地显示了记录时的时间序列(图 1.3)。y 轴以美元显示强生公司季度每股收益的价值,而 x 轴代表时间。它基本上是图 1.1 的再现,它显示了结合图 1.2 中的趋势图(Trend)、季节性图(Seasonal)和残差图(Residuals)的结果。

图 1.3 关注观察图(Observed)

然后是趋势组件,如图 1.4 所示。同样,请记住,y 轴代表值,而 x 轴仍然指的是时间。趋势定义为时间序列中缓慢变化的变化。我们可以看到它一开始是平的,然后急剧上升,这意味着我们的数据有一个增加或积极的趋势。

图 1.4 关注趋势部分。 我们的时间序列有一个明显的趋势,随着时间的推移,价值在不断增加。

趋势部分有时被称为水平。我们可以将趋势组件视为试图通过大多数数据点绘制一条线,以显示时间序列的大致方向。

接下来我们在图 1.5 中看到季节性成分。季节性组件捕获季节性变化,这是一个在固定时间段内发生的循环。 我们可以看到,在一年或四个季度的过程中,每股收益从低位开始,然后在年底再次增加和减少。

图 1.5 关注季节性成分。 在这里,我们的时间序列存在周期性波动,这表明收益每年都会上升和下降。

请注意 y 轴如何显示负值。这是否意味着每股收益为负数?显然这不可能,因为我们的数据集严格具有正值。因此,我们可以说季节性成分显示了我们如何偏离趋势。有时是正偏差,中会得到一个峰值。其他时候,我们会是负偏差,在观察图(Observed)中看到的是一个低谷。

最后,图 1.2 中的最后一张图显示了残差,这是趋势或季节性成分都无法解释的。我们可以将残差视为将趋势图和季节性图相加,并将每个时间点的值与观察到的图进行比较。对于某些点,我们可能会得到与 Observed 完全相同的值,在这种情况下,残差将为零。在其他情况下,该值与 Observed 中的值不同,因此 Residuals 图显示了必须将什么值添加到 Trend 和 Seasonal 才能调整结果并获得与 Observed 中相同的值。残差通常对应于随机误差,也称为白噪声,我们将在第 3 章讨论。它们代表了我们无法建模或预测的信息,因为它完全是随机的,如图 1.6 所示。

图 1.6 关注残差。 残差无法用趋势和季节性成分来解释。

时间序列分解
时间序列分解是我们将时间序列分成其组成部分的过程:趋势、季节性和残差。
趋势表示时间序列中缓慢移动的变化。 它负责使系列随时间逐渐增加或减少。
季节性成分表示系列中的季节性模式。 这些循环在固定的时间段内重复发生。
残差代表趋势和季节性成分无法解释的行为。 它们对应于随机误差,也称为白噪声。

我们已经可以直观地看到每个组件在预测时如何影响我们的工作。如果时间序列揭示了某种趋势,那么我们预计它会在未来继续下去。同样的,如果我们观察到强烈的季节性效应,这种情况很可能会持续下去,我们的预测必须反映这一点。在本书的后面,您将看到如何考虑这些组件并将它们包含在您的模型中以预测更复杂的时间序列。

1.2 时间序列预测的鸟瞰图

预测是使用历史数据和可能影响我们预测的未来事件的知识来预测未来。这个定义充满了希望,作为数据科学家,我们通常非常渴望通过使用我们的科学知识来展示一个具有近乎完美预测准确性的令人难以置信的模型来开始预测。但是,在达到预测点之前必须完成一些重要步骤。

图 1.7 是一个完整的预测项目在专业环境中的简化图。请注意,这些步骤不是通用的,可能会也可能不会遵循,具体取决于组织及其成熟度。尽管如此,这些步骤对于确保数据团队和业务团队之间的良好凝聚力至关重要,从而提供业务价值并避免团队之间的摩擦和挫折。

让我们深入探讨一个场景,该场景详细涵盖了预测项目路线图的每个步骤。想象一下,您计划从现在开始一个月后进行为期一周的露营旅行,并且您想知道随身携带哪种睡袋,以便晚上睡得舒服。

  • 设定一个目标
  • 确定必须预测什么才能实现我们的目标
  • 设定预测范围
  • 收集数据
  • 开发预测模型
  • 部署到生产环境
  • 监视其运行情况
  • 收集新数据(重复5、6、7、8)

图 1.7 预测项目路线图

第一步自然是设定一个目标,证明预测的必要性。然后,您必须确定需要预测什么才能实现该目标。然后你设定预测的范围。完成后,您可以收集数据并开发预测模型。然后将模型部署到生产中,监控其性能,并收集新数据以重新训练预测模型并确保它仍然相关。

1.2.1 设定目标

任何项目路线图的第一步都是设定目标。这里在场景中很明确:您想知道晚上带哪个睡袋舒适地入睡。如果晚上会很冷,温暖的睡袋是最好的选择。 当然,如果预计夜晚会很温暖,那么轻便的睡袋将是更好的选择。

1.2.2 确定实现目标必须预测的内容

然后你开始确定必须预测什么,以便你决定带哪个睡袋。在这种情况下,您需要预测夜间的温度。为简化起见,让我们考虑预测最低温度足以做出决定,并且最低温度发生在晚上。

1.2.3 设定预测范围

现在您可以设置预测范围。 在这种情况下,您的露营之旅是从现在开始的一个月,并且将持续一周。因此,我们有了一周的时间范围,因为您只对预测露营旅行期间的最低温度感兴趣。

1.2.4 收集数据

您现在可以开始收集数据了。例如,您可以收集历史每日最低温度数据。您还可以收集有关可能影响温度的可能因素的数据,例如湿度和风速。

这就到了具体多少数据才算够用的问题了。理想情况下,您应该收集超过1年的数据。这样,你就可以确定是否存在年度季节性模式或趋势。就温度而言,你当然可以期待一年四季的一些季节模式,因为不同的季节会带来不同的最低温度。

然而,1年的数据并不是多少数据就足够的最终答案。这在很大程度上取决于预报的频率。在这种情况下,您将创建每日预测,因此1年的数据应该足够了。

如果您想创建每小时的预报,几个月的训练数据就足够了,因为它将包含大量的数据点。如果要创建月度或年度预测,则需要更长的历史时期,以获得足够的数据点进行训练。

最后,对于训练模型所需的数据量没有明确的答案。确定这一点是构建模型、评估其性能并测试更多数据是否能提高模型性能的实验过程的一部分。

1.2.5 开发预测模型

有了您的历史数据,您就可以开发预测模型了。项目路线图的这一部分是整本书的重点。这是您研究数据并确定是否存在趋势或季节性模式的时候。

如果您观察季节性,那么 SARIMA 模型将是相关的,因为该模型使用季节性影响来产生预测。如果您有关于风速和湿度的信息,您可以使用 SARIMAX 模型将其考虑在内,因为您可以使用来自外生变量的信息(例如风速和湿度)为其提供信息。我们将在第 8 章和第 9 章详细探讨这些模型。

如果您设法收集了大量数据,例如过去 20 年的每日最低温度,您可以使用神经网络来利用这些非常大量的训练数据。 与统计学习方法不同,深度学习往往会产生更好的模型,因为更多的数据用于训练。

无论您开发哪种模型,您都将使用部分训练数据作为测试集来评估模型的性能。 测试集将始终是最新的数据点,并且必须代表预测范围。

在这种情况下,由于您的时间范围是一周,您可以从训练集中移除最后七个数据点,将它们放入测试集中。然后,当每个模型都经过训练后,您可以生成一周的预测并将结果与测试集进行比较。 可以通过计算误差度量来评估模型的性能,例如均方误差 (MSE)。 这是一种评估您的预测与实际值相差多远的方法。MSE 最低的模型将是您表现最好的模型,它将进入下一步。

1.2.6 部署到生产环境

拥有冠军模型后,您必须将其部署到生产环境中。这意味着您的模型可以接收数据并返回对未来 7 天的最低每日温度的预测。将模型部署到生产环境有很多方法,这可能是整本书的主题。您的模型可以用作 API 或集成到 Web 应用程序中,或者您可以定义自己的 Excel 函数来运行模型。最终,当您可以输入数据并返回预测而无需对数据进行任何手动操作时,您的模型就会被视为已部署。此时,您的模型可以被监控。

1.2.7 监控

由于露营之旅是从现在开始的 1 个月,您可以看到您的模型的表现如何。 每天您都可以将模型的预测与当天记录的实际最低温度进行比较。从而确定模型预测的质量。

您还可以通过查找意外事件。 例如,可能因为出现的热浪,降低了模型预测的质量。 密切监视您的模型和当前事件可以让您确定意外事件是否由临时情况引起,或者是否会持续接下来的 2 个月,在这种情况下,它可能会影响您对露营旅行的决定。

1.2.8 收集新数据

通过监控您的模型,您必须在将模型的预测与当天观察到的最低温度进行比较时收集新数据。然后可以使用这些新的、更新的数据来重新训练您的模型。 这样,您就可以使用最新数据来预测未来 7 天的最低温度。

这个循环在接下来的一个月里不断重复,直到到达露营的那一天,如图 1.8 所示。到那时,您将做出许多预测,根据新观察到的数据评估它们的质量,并在您记录它们时使用新的每日最低温度重新训练您的模型。 这样,您可以确保您的模型仍然有效,并使用相关数据来预测您的野营旅行的温度。

最后,根据模型的预测,您可以决定随身携带哪个睡袋。

  • 开发预测模型
  • 部署到生产环境
  • 监视其运行情况
  • 收集新数据

图 1.8 可视化生产循环。 模型投入生产后,您将进入一个监控模型、收集新数据并使用该数据调整预测模型的周期,然后再次部署它。

1.3 时间序列预测与其他回归任务有何不同

您可能遇到过回归任务,您必须在给定一组特征的情况下预测一些连续目标。乍一看,时间序列预测似乎是一个典型的回归问题:我们有一些历史数据,希望建立一个数学表达式,将未来值表示为过去值的函数。然而,时间序列预测和时间无关情景的回归之间存在一些关键差异,在我们研究第一个预测技术之前,应该解决这个问题。

1.3.1 时间序列有顺序

要记住的第一个概念是时间序列有一个顺序,我们不能在建模时改变这个顺序。 在时间序列预测中,我们将未来值表示为过去值的函数。 因此,我们必须保持数据有序,以免破坏这种关系。

此外,保持数据有序是有意义的,因为您的模型只能使用从过去到现在的信息——它不知道将来会观察到什么。 回想一下你的露营之旅。 如果你想预测周二的温度,你不可能使用周三的信息,因为从模型的角度来看,它是未来的。 您将只能使用周一及之前的数据。 这就是为什么数据的顺序在整个建模过程中必须保持不变。

机器学习中的其他回归任务通常没有顺序。 例如,如果您的任务是根据广告支出预测收入,那么何时在广告上花费了一定金额并不重要。 相反,您只想将广告支出金额与收入联系起来。 事实上,您甚至可以随机打乱数据以使您的模型更加健壮。 这里的回归任务是简单地导出一个函数,使得给定广告支出金额,返回收入的估计值。

另一方面,时间序列按时间索引,并且必须保持该顺序。 否则,您将使用预测时没有的未来信息来训练您的模型。这在更正式的术语中称为前瞻偏差。 由此生成的模型将不可靠,并且在您进行未来预测时很可能表现不佳。

1.3.2 时间序列有时没有特征

可以在不使用时间序列本身以外的特征的情况下预测时间序列。

作为数据科学家,我们习惯于拥有包含许多列的数据集,每个列代表我们目标的潜在预测因子。 例如,考虑基于广告支出预测收入的任务,其中收入是目标变量。 作为特征,我们可以得到花在 Google 广告、Facebook 广告和电视广告上的金额。 使用这三个特征,我们将建立一个回归模型来估计收入。

但是,对于时间序列,给定一个包含时间列和该时间点值的简单数据集是很常见的。 在没有任何其他特征的情况下,我们必须学习使用时间序列的过去值来预测未来值的方法。 这是移动平均模型(第 4 章)或自回归模型(第 5 章)发挥作用的时候,因为它们是将未来值表示为过去值的函数的方法。 这些模型是更复杂模型的基础,然后允许您考虑时间序列中的季节性模式和趋势。 从第 6 章开始,我们将逐步建立在这些基本模型的基础上来预测更复杂的时间序列。

1.4 后续步骤

本书将详细介绍不同的预测技术。 我们将从一些非常基本的方法开始,例如移动平均模型和自回归模型,我们将逐渐考虑更多因素,以便使用 ARIMA、SARIMA 和 SARIMAX 模型预测具有趋势和季节模式的时间序列。 我们还将使用高维时间序列,这将要求我们对序列数据使用深度学习技术。 因此,我们将不得不使用 CNN(卷积神经网络)和 LSTM(长短期记忆)来构建神经网络。 最后,您将学习如何自动化预测时间序列的工作。 如前所述,本书中的所有实现都将在 Python 中完成。

现在您已经了解了时间序列是什么以及预测与您之前可能见过的任何传统回归任务有何不同,我们准备继续并开始预测。 然而,我们在预测方面的第一次尝试将集中在用作基线模型的简单方法上。

本章要点

  • 时间序列是按时间排序的一组数据点。
  • 时间序列的示例是股票的收盘价或室外温度。
  • 时间序列可以分解为三个部分:趋势、季节性部分和残差。
  • 在预测时设定一个目标并在部署模型后对其进行监控非常重要。 这将确保项目的成功和长久。
  • 建模时切勿更改时间序列的顺序。
]]>
https://minuo.org/time-series-forecasting-in-python-chapter-1-understanding-time-series-forecasting/feed 0
Python中使用enumerate函数跟踪当前项的序号 https://minuo.org/use-the-enumerate-function-to-track-the-sequence-number-of-the-current-item https://minuo.org/use-the-enumerate-function-to-track-the-sequence-number-of-the-current-item#respond Sun, 29 May 2022 16:28:00 +0000 https://minuo.org/?p=1017 有时候我们可能想跟踪使用当前迭代项的序号,通常的做法可能是这样的:

]]>
有时候我们可能想跟踪使用当前迭代项的序号,通常的做法可能是这样的:

i=0
for value in collection:
   # do something with value
   i += 1

实际上,Python 内建了一个函数(enumrate)可以返回一个(i, value)的元组序列:

for i, value in enumerate(collection):
   # do something with value

省事多了……

]]>
https://minuo.org/use-the-enumerate-function-to-track-the-sequence-number-of-the-current-item/feed 0
使用“virtualenv”创建虚拟环境 https://minuo.org/create-a-virtual-environment-using-virtualenv https://minuo.org/create-a-virtual-environment-using-virtualenv#respond Mon, 17 Jan 2022 14:44:21 +0000 https://minuo.org/?p=865 与其他编程语言一样,Python 有自己的包(或库)的管理方式。默认情况下,您机器上的每个 Python 项目都将使用默认的 Python 站点包目录(该目录与主要 Python 安装相关联,并表示为基本(根)环境)。

]]>
与其他编程语言一样,Python 有自己的包(或库)的管理方式。默认情况下,您机器上的每个 Python 项目都将使用默认的 Python 站点包目录(该目录与主要 Python 安装相关联,并表示为基本(根)环境)。

Python 虚拟环境的主要目的是为 Python 项目创建一个隔离的环境。每个项目都可以有自己的依赖项,而不管其他项目有什么依赖项。

使用“virtualenv”创建虚拟环境

安装virtualenv

检查一下环境中是否有virtualenv

which virtualenv

如果没有使用命令

pip install virtualenv

创建虚拟环境

virtualenv <my_env_name>

使用特定版本的 Python 创建环境,可以使用您选择的 Python 解释器(如 python2.7)

virtualenv -p /usr/bin/python2.7 <my_env_name>

requirements.txt 文件创建环境,通常采取的步骤是:

virtualenv <my_env_name> 创建一个新环境

source <my_env_name>/bin/activate 激活新环境

pip install -r requirements.txt 安装当前环境的依赖

或者,也可以考虑使用结合了 pip virtualenvpipenv

激活虚拟环境

可以通过运行以下命令来激活虚拟环境:

source <my_env_name>/bin/activate

停用虚拟环境

要停用当前环境,您可以键入:

deactivate

检查当前运行的环境

您可以通过运行 which python which pip 快速验证您是否在环境中,如果一切顺利,它将返回环境中 python 可执行文件的路径。

删除环境

sudo rm -rf <my_env_name>

以上是一些常用的使用方法。

谢谢阅读。

]]>
https://minuo.org/create-a-virtual-environment-using-virtualenv/feed 0
Python简单数据类型(整数、浮点数) https://minuo.org/python-simple-data-type-integers-and-float https://minuo.org/python-simple-data-type-integers-and-float#comments Wed, 20 Oct 2021 18:17:33 +0000 https://minuo.me/?p=640 Python语言中数据类型有整数、浮点数、字符串、列表、元组、字典等,我们先看简单的。

整数

类似-10,0,10,99,100等,通过以下代码定义一个整数类型。

#定义整数类型
userAge = 30
userNumber = 66773277

Python中可以对整数执行加、减、乘、除等操作如前面例子中的。

# 操作符
x = 5
y = 2
print(x+y) # 7
print(x-y) # 3
print(x*y) # 10
print(x/y) # 2.5
print(x//y) # 2
print(x**y) # 25

浮点数

python中带小数点的数字都称为浮点数。比如3.1415,-0.618, 9.8182等。

# 定义浮点数变量
userHeight = 1.75 #m
userWeight = 69.2 #kg
# 这里需要注意的是在计算过程中结果包含的小数位数可能是不确定的(在IDLE中可以看到类似下面的结果)
>>> 0.2+0.1
0.30000000000000004
>>> 0.2*0.1
0.020000000000000004
>>> 

使用str()避免类型错误

且看如下代码:

userName = '米诺'
userAge = 30
message = userAge + '岁的' + userName + '学Python'
print(message)

上面的代码不会打印我们期望的输出“30岁的米诺学Python”。反而会触发如下错误:

Traceback (most recent call last):
  File "/Users/minuo/python_study/day_01/variables.py", line 29, in <module>
    message = userAge + '岁的' + userName + '学Python'
TypeError: unsupported operand type(s) for +: 'int' and 'str'

这是一个类型错误,表示Python不知道该如何处理变量userAge的值。所以这里我们需要通过调用内置函数str()显式的说明我们要使用的数据类型是字符串,从而让Python将这个非字符串的值转换为字符串。

这样,我们就能得到我们想要的结果了。

userName = '米诺'
userAge = 30
message = str(userAge) + '岁的' + userName + '学Python'
print(message) # 30岁的米诺学Python

类似这样的错误,可能会经常出现,因此应小心使用变量,关注变量隐匿转换。

]]>
https://minuo.org/python-simple-data-type-integers-and-float/feed 2