标签归档:数据分析

Tsmoothie 这个神奇的Python库,可以将数据平滑化并找到异常点

在处理数据的时候,我们经常会遇到一些非连续的散点时间序列数据:

有些时候,这样的散点数据是不利于我们进行数据的聚类和预测的。因此我们需要把它们平滑化,如下图所示:

将散点都去除,平滑后的效果如下:

​这样的时序数据是不是看起来舒服多了?​此外,使用平滑后的时序数据去做聚类或预测或许有令人惊艳的效果,因为它去除了一些偏差值并细化了数据的分布范围。

如果我们自己开发一个这样的平滑工具,会耗费不少的时间。​因为平滑的技术有很多种,你需要一个个地去研究,找到最合适的技术并编写代码,这是一个非常耗时的过程。平滑技术包括但不限于:

  • 指数平滑
  • 具有各种窗口类型(常数、汉宁、汉明、巴特利特、布莱克曼)的卷积平滑
  • 傅立叶变换的频谱平滑
  • 多项式平滑
  • 各种样条平滑(线性、三次、自然三次)
  • 高斯平滑
  • 二进制平滑

所幸,有大佬已经为我们实现好了时间序列的这些平滑技术,并在GitHub上开源了这份模块的代码——它就是 tsmoothie。

下面就让我们来试一下 tsmoothie.

1.准备

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 进行安装。

(可选1) 如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.

(可选2) 此外,推荐大家用VSCode编辑器来编写小型Python项目:Python 编程的最好搭档—VSCode 详细指南

Windows环境下打开Cmd(开始—运行—CMD),苹果系统环境下请打开Terminal(command+空格输入Terminal),输入命令安装依赖:

pip install tsmoothie

PS, Tsmoothie仅支持Python 3.6 及以上的版本。

2.Tsmoothie 基本使用

为了尝试Tsmoothie的效果,我们需要生成随机数据:

import numpy as np
import matplotlib.pyplot as plt
from tsmoothie.utils_func import sim_randomwalk
from tsmoothie.smoother import LowessSmoother

# 生成 3 个长度为200的随机数据组
np.random.seed(123)
data = sim_randomwalk(n_series=3, timesteps=200, 
                      process_noise=10, measure_noise=30)

然后使用Tsmoothie执行平滑化:

# 平滑
smoother = LowessSmoother(smooth_fraction=0.1, iterations=1)
smoother.smooth(data)

通过 smoother.smooth_data 你就可以获取平滑后的数据:

print(smoother.smooth_data)
# [[   5.21462928    3.07898076    0.93933646   -1.19847767   -3.32294934 
#     -5.40678762   -7.42425709   -9.36150892  -11.23591897  -13.05271523 
#      .......       .......       .......      .......       .......   ]]

绘制效果图:

# 生成范围区间
low, up = smoother.get_intervals('prediction_interval')

plt.figure(figsize=(18,5))

for i in range(3):
    
    plt.subplot(1,3,i+1)
    plt.plot(smoother.smooth_data[i], linewidth=3, color='blue')
    plt.plot(smoother.data[i], '.k')
    plt.title(f"timeseries {i+1}"); plt.xlabel('time')

    plt.fill_between(range(len(smoother.data[i])), low[i], up[i], alpha=0.3)

3.基于Tsmoothie的极端异常值检测

事实上,基于smoother生成的范围区域,我们可以进行异常值的检测:

可以看到,在蓝色范围以外的点,都属于异常值。我们可以轻易地将这些异常值标红或记录,以便后续的处理。

_low, _up = smoother.get_intervals('sigma_interval', n_sigma=2)
series['low'] = np.hstack([series['low'], _low[:,[-1]]])
series['up'] = np.hstack([series['up'], _up[:,[-1]]])
is_anomaly = np.logical_or(
    series['original'][:,-1] > series['up'][:,-1], 
    series['original'][:,-1] < series['low'][:,-1]
).reshape(-1,1)

假设蓝色范围interval的最大值为up、最小值为low,如果存在 data > up 或 data < low 则表明此数据是异常点。

使用以下代码通过滚动数据点进行平滑化和异常检测,就能保存得到上方的GIF动图。

# https://github.com/cerlymarco/MEDIUM_NoteBook/blob/master/Anomaly_Detection_RealTime/Anomaly_Detection_RealTime.ipynb

import numpy as np
import matplotlib.pyplot as plt
from celluloid import Camera
from collections import defaultdict
from functools import partial
from tqdm import tqdm

from tsmoothie.utils_func import sim_randomwalk, sim_seasonal_data
from tsmoothie.smoother import *


def plot_history(ax, i, is_anomaly, window_len, color='blue', **pltargs):
    
    posrange = np.arange(0,i)
    
    ax.fill_between(posrange[window_len:], 
                    pltargs['low'][1:], pltargs['up'][1:], 
                    color=color, alpha=0.2)
    if is_anomaly:
        ax.scatter(i-1, pltargs['original'][-1], c='red')
    else:
        ax.scatter(i-1, pltargs['original'][-1], c='black')
    ax.scatter(i-1, pltargs['smooth'][-1], c=color)
    
    ax.plot(posrange, pltargs['original'][1:], '.k')
    ax.plot(posrange[window_len:], 
            pltargs['smooth'][1:], color=color, linewidth=3)
    
    if 'ano_id' in pltargs.keys():
        if pltargs['ano_id'].sum()>0:
            not_zeros = pltargs['ano_id'][pltargs['ano_id']!=0] -1
            ax.scatter(not_zeros, pltargs['original'][1:][not_zeros], 
                       c='red', alpha=1.)

np.random.seed(42)

n_series, timesteps = 3, 200

data = sim_randomwalk(n_series=n_series, timesteps=timesteps, 
                      process_noise=10, measure_noise=30)

window_len = 20

fig = plt.figure(figsize=(18,10))
camera = Camera(fig)

axes = [plt.subplot(n_series,1,ax+1) for ax in range(n_series)]
series = defaultdict(partial(np.ndarray, shape=(n_series,1), dtype='float32'))

for i in tqdm(range(timesteps+1), total=(timesteps+1)):
    
    if i>window_len:
    
        smoother = ConvolutionSmoother(window_len=window_len, window_type='ones')
        smoother.smooth(series['original'][:,-window_len:])

        series['smooth'] = np.hstack([series['smooth'], smoother.smooth_data[:,[-1]]]) 

        _low, _up = smoother.get_intervals('sigma_interval', n_sigma=2)
        series['low'] = np.hstack([series['low'], _low[:,[-1]]])
        series['up'] = np.hstack([series['up'], _up[:,[-1]]])

        is_anomaly = np.logical_or(
            series['original'][:,-1] > series['up'][:,-1], 
            series['original'][:,-1] < series['low'][:,-1]
        ).reshape(-1,1)
        
        if is_anomaly.any():
            series['ano_id'] = np.hstack([series['ano_id'], is_anomaly*i]).astype(int)
            
        for s in range(n_series):
            pltargs = {k:v[s,:] for k,v in series.items()}
            plot_history(axes[s], i, is_anomaly[s], window_len, 
                         **pltargs)

        camera.snap()
        
    if i>=timesteps:
        continue
    
    series['original'] = np.hstack([series['original'], data[:,[i]]])

    
print('CREATING GIF...')  # it may take a few seconds
camera._photos = [camera._photos[-1]] + camera._photos
animation = camera.animate()
animation.save('animation1.gif', codec="gif", writer='imagemagick')
plt.close(fig)
print('DONE')

注意,异常点并非都是负面作用,在不同的应用场景下,它们可能代表了不同的意义。

比如在股票中,它或许可以代表着震荡行情中某种趋势反转的信号。

或者在家庭用电量分析中,它可能代表着某个时刻的用电峰值,根据这个峰值我们可以此时此刻开启了什么样的电器。

所以异常点的作用需要根据不同应用场景进行不同的分析,才能找到它真正的价值。

总而言之,Tsmoothie 不仅可以使用多种平滑技术平滑化我们的时序数据,还可以根据平滑结果找出数据中的离群点,是我们做数据分析和研究的一个好帮手,非常有价值。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

什么格式是保存 Pandas 数据的最好格式?

在数据分析相关项目工作时,我通常使用Jupyter笔记本和pandas库来处理和移动我的数据。对于中等大小的数据集来说,这是一个非常直接的过程,你甚至可以将其存储为纯文本文件而没有太多的开销。

然而,当你的数据集中的观测数据数量较多时,保存和加载数据回内存的过程就会变慢,现在程序的重新启动都会迫使你等待数据重新加载。所以最终,CSV文件或任何其他纯文本格式都会失去吸引力。

我们可以做得更好。有很多二进制格式可以用来将数据存储到磁盘上,其中有很多格式pandas都支持。我们怎么能知道哪一种更适合我们的目的呢?

来吧,我们尝试其中的几个,然后进行对比!这就是我决定在这篇文章中要做的:通过几种方法将 pandas.DataFrame 保存到磁盘上,看看哪一种在I/O速度、内存消耗和磁盘空间方面做的更好。

在这篇文章中,我将展示我的测试结果。

1.要比较的格式

我们将考虑采用以下格式来存储我们的数据:

1. CSV — 数据科学家的一个好朋友
2. Pickle — 一种Python的方式来序列化事物
3. MessagePack — 它就像JSON,但又快又小
4. HDF5 — 一种设计用于存储和组织大量数据的文件格式
5. Feather — 一种快速、轻量级、易于使用的二进制文件格式,用于存储数据框架
6. Parquet — Apache Hadoop的柱状存储格式

所有这些格式都是被广泛使用的,而且(也许除了MessagePack)在你做一些数据分析的事情时非常经常遇到。

为了追求找到最好的缓冲格式来存储程序会话之间的数据,我选择了以下指标进行比较。

1. size_mb – 文件大小(Mb)。
2. save_time – 将数据帧保存到磁盘上所需的时间量。
3. load_time – 将之前转储的数据帧加载到内存中所需要的时间量。
4. save_ram_delta_mb – 数据帧保存过程中最大的内存消耗增长量。
5. load_ram_delta_mb – 数据帧加载过程中的最大内存消耗增长量。

请注意,当我们使用高效压缩的二进制数据格式,如 Parquet 时,最后两个指标变得非常重要。它们可以帮助我们估计加载序列化数据所需的内存量,此外还有数据大小本身。我们将在接下来的章节中更详细地讨论这个问题。

2.测试及结果

我决定使用一个合成数据集进行测试,以便更好地控制序列化的数据结构和属性。

另外,我在我的基准中使用了两种不同的方法:

(a) 将生成的分类变量保留为字符串。

(b) 在执行任何I/O之前将它们转换为 pandas.Categorical 数据类型。

函数generate_dataset显示了我在基准中是如何生成数据集的:

def generate_dataset(n_rows, num_count, cat_count, max_nan=0.1, max_cat_size=100):
    """
    随机生成具有数字和分类特征的数据集。
    
    数字特征取自正态分布X ~ N(0, 1)。
    分类特征则被生成为随机的uuid4字符串。
    
    此外,数字和分类特征的max_nan比例被替换为NaN值。
    """
    dataset, types = {}, {}
    
    def generate_categories():
        from uuid import uuid4
        category_size = np.random.randint(2, max_cat_size)
        return [str(uuid4()) for _ in range(category_size)]
    
    for col in range(num_count):
        name = f'n{col}'
        values = np.random.normal(0, 1, n_rows)
        nan_cnt = np.random.randint(1, int(max_nan*n_rows))
        index = np.random.choice(n_rows, nan_cnt, replace=False)
        values[index] = np.nan
        dataset[name] = values
        types[name] = 'float32'
        
    for col in range(cat_count):
        name = f'c{col}'
        cats = generate_categories()
        values = np.array(np.random.choice(cats, n_rows, replace=True), dtype=object)
        nan_cnt = np.random.randint(1, int(max_nan*n_rows))
        index = np.random.choice(n_rows, nan_cnt, replace=False)
        values[index] = np.nan
        dataset[name] = values
        types[name] = 'object'
    
    return pd.DataFrame(dataset), types

我们将CSV文件的保存和加载性能作为一个基准。

五个随机生成的具有一百万个观测值的数据集被转储到CSV中,并读回内存以获得平均指标。

每种二进制格式都针对20个随机生成的具有相同行数的数据集进行测试。

这些数据集包括15个数字特征和15个分类特征。你可以在这个资源库中找到带有基准测试功能和所需的完整源代码:

https://github.com/devforfu/pandas-formats-benchmark

或在Python实用宝典后台回复 Pandas IO对比 ,下载完整代码。

(a) 数据为字符串特征时的性能

下图显示了每种数据格式的平均I/O时间。一个有趣的观察是,hdf显示出比csv更慢的加载速度,而其他二进制格式的表现明显更好。其中最令人印象深刻的是feather和parquet。

在保存数据和从磁盘上读取数据时,内存开销如何?

下一张图片告诉我们,hdf 的表现就不是那么好了。可以肯定的是,csv在保存/加载纯文本字符串时不需要太多的额外内存,而Feather和parquet则相当接近:

最后,让我们看看文件的大小。这次parquet显示了一个令人印象深刻的结果,考虑到这种格式是为有效存储大量数据而开发的,这并不令人惊讶。

(b) 字符串特征转换为数字时的性能

在上一节中,我们没有尝试有效地存储我们的分类特征而是使用普通的字符串。让我们来弥补这个遗漏吧! 这一次我们使用一个专门的 pandas.Categorical 类型,转字符串特征为数字特征。

看看现在与纯文本的csv相比,它看起来如何!

现在所有的二进制格式都显示出它们的真正力量。Csv的基准结果已经远远落后了,所以让我们把它去掉,以便更清楚地看到各种二进制格式之间的差异:

Feather 和 Pickle 显示了最好的 I/O 速度,而 hdf 仍然显示了明显的性能开销。

现在是时候比较数据进程加载时的内存消耗了。下面的柱状图显示了我们之前提到的关于parquet格式的一个重要事实。

可以看到 parquet 读写时的内存空间差距有多大,你有可能你无法将比较大的 parquet 文件加载到内存中。

最后的图显示了各格式的文件大小。所有的格式都显示出良好的效果,除了hdf仍然需要比其他格式多得多的空间:

3.结论

正如我们的测试所显示的,似乎 feather 格式是存储Python会话数据的理想候选者。它显示了很快的I/O速度,在磁盘上不占用太多内存,并且在加载回RAM时不需要消耗太大的内存。

当然,这种比较并不意味着你应该在每个可能的情况下使用这种格式。例如,feather格式一般不会被用作长期文件存储的格式。

另外,某些特定情况下也无法使用 feather,这由你的整个程序架构决定。然而,就如本帖开头所述的目的,它在不被任何特殊事项限制的情况下是一个很好的选择。

本文译自 towardsdatascience
作者: Ilia Zaitsev
有部分修改。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Sweetviz 让你三行代码实现数据分析

Sweetviz是一个开源Python库,它只需三行代码就可以生成漂亮的高精度可视化效果来启动EDA(探索性数据分析)。输出一个HTML。

如上图所示,它不仅能根据性别、年龄等不同栏目纵向分析数据,还能对每个栏目做众数、最大值、最小值等横向对比。

所有输入的数值、文本信息都会被自动检测,并进行数据分析、可视化和对比,最后自动帮你进行总结,是一个探索性数据分析的好帮手。

1.准备

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 进行安装。

(可选1) 如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.

(可选2) 此外,推荐大家用VSCode编辑器来编写小型Python项目:Python 编程的最好搭档—VSCode 详细指南

Windows环境下打开Cmd(开始—运行—CMD),苹果系统环境下请打开Terminal(command+空格输入Terminal),输入命令安装依赖:

pip install sweetviz

2.sweetviz 基本用法

sweetviz 使用的原理是,使用一行代码,生成一个数据报告的对象(其中,my_dataframe是pandas中的DataFrame,一种表格型数据结构):

import pandas as pd
import sweetviz as sv

# 读取数据
my_dataframe = pd.read_csv('../ImpartData/iris.csv')
# 分析数据
my_report = sv.analyze(my_dataframe)
# 生成报告
my_report.show_html()

执行完成后,会在当前文件夹下生成一个HTML的报告文件

双击这个html,你就能看到精美的分析报告了:

其中,分析数据有三种函数可以用,除了上面提到的analyze函数,还有 compare 和 compare_intra 函数。

首先是analyze函数:

analyze(source: Union[pd.DataFrame, Tuple[pd.DataFrame, str]],
            target_feat: str = None,
            feat_cfg: FeatureConfig = None,
            pairwise_analysis: str = 'auto')

可见其有以下4个参数可以配置:

  • source:以pandas中的DataFrame数据结构作为分析对象。
  • target_feat:需要被标记为目标对象的字符串。
  • feat_cfg:需要被跳过、或是需要被强制转换为某种数据类型的特征。
  • pairwise_analysis:相关性分析可能需要花费较长时间。如果超过了你的忍受范围,就需要设置这个参数为on或者off,以判断是否需要分析数据相关性。

compare()丨两个数据集比较

my_report = sv.compare([my_dataframe, "Training Data"], [test_df, "Test Data"], "Survived", feature_config)

要比较两个数据集,只需使用该 compare() 函数。它的参数与 analyze() 相同,只是插入了第二个参数来覆盖比较数据帧。建议使用 [dataframe, “name”] 参数格式以更好地区分基础数据帧和比较数据帧。(例如 [my_df, "Train"] 比 my_df 更好)

compare_intra()丨数据集栏目比较

my_report = sv.compare_intra(my_dataframe, my_dataframe["Sex"] == "male", ["Male", "Female"], feature_config)

想要对数据集中某个栏目下的参数进行分析,就采用这个函数进行。
例如,如果需要比较“性别”栏目下的“男性”和“女性”,就可以采用这个函数。

3.调整报告布局

一旦你创建了你的报告对象,只需将它传递给两个show函数中的一个:

1. show_html():

show_html(  filepath='SWEETVIZ_REPORT.html', 
            open_browser=True, 
            layout='widescreen', 
            scale=None)

show_html(…)将在当前文件路径中创建并保存 HTML 报告。有以下参数:

  • layout (布局):无论是'widescreen''vertical'。当鼠标移过每个功能时,宽屏布局会在屏幕右侧显示详细信息。新的(从 2.0 开始)垂直布局在水平方向上更加紧凑,并且可以在单击时扩展每个细节区域。
  • scale:使用浮点数(scale= 0.8None)来缩放整个报告。
  • open_browser:启用 Web 浏览器的自动打开以显示报告。如果不需要,可以在此处禁用它。

2.show_notebook():

show_notebook(  w=None, 
                h=None, 
                scale=None,
                layout='widescreen',
                filepath=None)

它将嵌入一个 IFRAME 元素,在notebook中显示报告(例如 Jupyter、Google Colab 等)。

请注意,由于Notebook通常是一个更受限制的环境,因此使用自定义宽度/高度/比例值 ( whscale) 可能是个好主意。选项是:

  • w(宽度):设置报告输出窗口的宽度。可以是百分比字符串 ( w="100%") 或像素 (w=900)。
  • h(高度):设置报告输出窗口的高度。可以是像素数 ( h=700) 或将窗口拉伸到与所有特征 ( h="Full")一样高。
  • scale:与上面的 show_html 相同。
  • layout:与上面的 show_html 相同。
  • scale:与上面的 show_html 相同。
  • filepath:可选的输出 HTML 报告。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

太酷炫了,我用python画出了北上广深的地铁路线动态图

今天教大家用python制作北上广深——地铁线路动态图,这可能是全网最全最详细的教程了。

坐标点的采集

小五之前做过类似的地理可视化,不过都是使用网络上收集到的json数据。但很多数据其实是过时的,甚至是错误/不全的。所以我们最好还是要自己动手,丰衣足食(爬虫大法好)。

打开高德地图的地铁网页,http://map.amap.com/subway/index.html?&1100

可以轻松得到北京地铁数据的接口,同理也把其他三个城市的url复制出来。

有了api,解析json即可获得数据👇

url = 'http://map.amap.com/service/subway?_1615466846985&srhdata=1100_drw_beijing.json'
response = requests.get(url)
result = json.loads(response.text)
stations = []
for i in result['l']:
    station = []
    for a in i['st']:
        station.append([float(b) for b in a['sl'].split(',')])
    stations.append(station)
pprint.pprint(stations)

pprint格式化打印结果,方便预览

坐标系的转换

其实我之前有看到类似地理可视化文章,结果自己一试发现缩小看还行,一放大就会发现坐标点飘出二里地了😂

正好拿上文获取的坐标点给大家演示一下,看看同样的经纬度在不同地图里的地理位置👇

👆可以看到该经纬度在高德地图里指的是金安桥地铁站,然而在百度地图里,地理位置则指向了几公里外的某大厦。

为什么会出现这个问题呢?

其实是不同地图产品的地理坐标系导致的。

下面说一下常见的地理坐标系:地球坐标系是国际通用坐标系,比较适合国际地图可视化。不过在我国范围内,一般不会直接使用它,而是使用由国家测绘局在其基础上加密的火星坐标系。另外还有公司会在火星坐标系上进行二次加密,比如百度坐标系、搜狗坐标系等。

我网上找到了一张图,来自知乎@师大Giser[1]👇

上图可以作为参考,具体原因我们就不细究了。重点是什么,如何利用python转换坐标系?

例如在本文中,我们是在高德地图中获得的坐标点集合,那么也就是使用的是GCJ-02坐标系。而下文可视化中会调用百度地图的接口,也就是需要在BD-09坐标系中进行可视化。

幸好我在网上搜到了GCJ-02BD-09的公式,并用python实现此公式:

#需要的两个常量先设置好
pi = 3.1415926535897932384 
r_pi = pi * 3000.0/180.0

def gcj02_bd09(lon_gcj02,lat_gcj02):
    b = math.sqrt(lon_gcj02 * lon_gcj02 + lat_gcj02 * lat_gcj02) + 0.00002 * math.sin(lat_gcj02 * r_pi)
    o = math.atan2(lat_gcj02 , lon_gcj02) + 0.000003 * math.cos(lon_gcj02 * r_pi)
    lon_bd09 = b * math.cos(o) + 0.0065
    lat_bd09 = b * math.sin(o) + 0.006
    return [lon_bd09,lat_bd09]

这样我们就写好了一个python将GCJ-02坐标系转成BD-09的函数,调用这个函数,就可以将高德地图获取的坐标点集合统统转换成百度坐标系。

result = []
for station in stations:
    result.append([gcj02_bd09(*point) for point in station])

以其中一个坐标点为例:

到此,我们的前期数据工作终于准备齐了。

当然,如果我们一开始获取的数据就是BD_09(百度地图)坐标系的,转换这步就可以直接省略喽~

地理可视化

接下来就要利用pyecharts中的BMap来可视化了,不过需要先获取百度开放平台的密钥。

百度地图开放平台👉http://lbsyun.baidu.com/apiconsole/key#/home

登录百度账户,查看应用管理-我的应用。点击创建应用,全部默认随便创建。

复制👆上图中的访问应用(AK),保存好,这在后续的可视化中将要用到。

我们使用pyecharts中的BMap,先导入模块

from pyecharts.charts import BMap 
from pyecharts import options as opts 
from pyecharts.globals import BMapType, ChartType 

在导入数据(也就是上文转换后的经纬度数据result)后,可以调整一下参数以及增添一些控件。

👇关键参数都做了注释,方便大家查看(其中百度appkey记得替换成自己的)

map_b = (
    BMap(init_opts = opts.InitOpts(width = "800px", height = "600px"))
    .add_schema(
        baidu_ak = '****************'#百度地图开发应用appkey
        center = [116.40396339.915119], #当前视角的中心点
        zoom = 10#当前视角的缩放比例
        is_roam = True#开启鼠标缩放和平移漫游
    )
    .add(
        series_name = "",
        type_ = ChartType.LINES, #设置Geo图类型
        data_pair = result, #数据项
        is_polyline = True#是否是多段线,在画lines图情况下#
        linestyle_opts = opts.LineStyleOpts(color = "blue", opacity = 0.5, width = 1), # 线样式配置项
    )
    .add_control_panel(
        maptype_control_opts = opts.BMapTypeControlOpts(type_ = BMapType.MAPTYPE_CONTROL_DROPDOWN), #切换地图类型的控件
        scale_control_opts = opts.BMapScaleControlOpts(), #比例尺控件
        overview_map_opts = opts.BMapOverviewMapControlOpts(is_open = True), #添加缩略地图
        navigation_control_opts = opts.BMapNavigationControlOpts() #地图的平移缩放控件
    )
)

map_b.render(path = 'subway_beijing.html')

注:因为是北京地图,所以设置天安门的经纬度[116.403963, 39.915119]为视角中心。

让我们看一下可视化的结果吧:

👆上图中的四个角都有控件,这是我们在代码中添加了控件参数,它们分别为:地图的平移缩放控件、切换地图类型的控件、缩略地图、以及比例尺控件。

是不是还阔以

其他效果展示

上文已经基本实现了用python制作地铁线路动态图。不过大家都用同一种颜色背景制作动态图的话,就显得就太单调了。

正好我们还要绘制其他三个城市的地铁图,那就调整一些参数,看看能获得什么效果吧?

上海-变色

上海的数据接口是:

http://map.amap.com/service/subway?_1615467204533&srhdata=3100_drw_shanghai.json

上海市的地铁图我们改一下line的颜色,可在参数linestyle_opts中修改color。

👇下图中的线条颜色是lilac——浅紫色

广州-卫星图

广州的数据接口是:

http://map.amap.com/service/subway?_1615494419554&srhdata=4401_drw_guangzhou.json

其实我们还可以调整可视化背景为卫星图。不过这一操作并不需要额外写代码,因为刚刚上文提到我在调整参数时添加了4个控件,其中右上角的就可以直接切换地图类型,具体操作见下图。

深圳-个性化配色

深圳的数据接口是:

http://map.amap.com/service/subway?_1615494473615&srhdata=4403_drw_shenzhen.json

如果不满意百度地图设置好的地图背景,我们还可以个性化设置mapStyle,调整自己的配色styleJson

下图就是小五参考网上公开的配色方案制作的,大家也可以用来参考https://blog.csdn.net/weixin_41290949/article/details/106379134[2]

小结

今天带大家学习了如何利用python绘制一线城市的地铁线路动图。

主要分为四个部分:坐标点的采集、坐标系的转换、利用pyecharts地理可视化、其他效果展示。

如果你读完本文觉得有收获,希望可以给文章右下角点个赞👍

我们的文章到此就结束啦,如果你喜欢今天的Python 实战教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应红字验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

点击下方阅读原文可获得更好的阅读体验

Python实用宝典 (pythondict.com)
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python 电信用户流失预测模型实战教程

1.流失预测 研究背景

1、做好用户流失预测可以降低营销成本。老生常谈,新客户开发成本老客户维护成本的5倍。

2、获得更好的用户体验。并不是所有的增值服务都可以有效留住客户。

3、获得更高的销售回报。价格敏感型客户和非价格敏感性客户。

2.提出问题

1、流失客户有哪些显著性特征?

2、当客户在哪些特征下什么条件下比较容易发生流失

3.数据集描述

该数据是datafountain上的《电信客户流失数据》,这里提供一个下载地址。
https://www.datafountain.cn/datasets/35guide

该数据集有21个变量,7043个数据点。变量可分为以下三个部分:用户属性、用户行为、研究对象。

用户属性
customerID :用户ID
gender:性别(Female & Male)
SeniorCitizen :老年人(1表示是,0表示不是)
Partner :是否有配偶(Yes or No)
Dependents :是否经济独立(Yes or No)
tenure :客户的职位(0-72,共73个职位)

用户行为
PhoneService :是否开通电话服务业务(Yes or No)
MultipleLines :是否开通了多线业务(Yes 、No or No phoneservice 三种)
InternetService :是否开通互联网服务(No, DSL数字网络,fiber optic光纤网络 三种)
OnlineSecurity :是否开通网络安全服务(Yes,No,No internetserive 三种)
OnlineBackup :是否开通在线备份业务(Yes,No,No internetserive 三种)
DeviceProtection :是否开通了设备保护业务(Yes,No,No internetserive 三种)
TechSupport :是否开通了技术支持服务(Yes,No,No internetserive 三种)
StreamingTV :是否开通网络电视(Yes,No,No internetserive 三种)
StreamingMovies :是否开通网络电影(Yes,No,No internetserive 三种)
Contract :签订合同方式 (按月,一年,两年)
PaperlessBilling :是否开通电子账单(Yes or No)
PaymentMethod :付款方式(bank transfer,credit card,electronic check,mailed check)
MonthlyCharges :月费用
TotalCharges :总费用

研究对象Churn:该用户是否流失(Yes or No)

4.分析思路

分析视角分析方法的灵魂。

分析方法有上百种,但分析视角只有四种:

  • 对比视角
  • 分类视角
  • 相关视角
  • 描述视角

一旦将业务需求拆解成指标,接下来只需要针对每个指标进行分析视角四选一即可。

数据集描述,已经将变量分为三个维度了:用户属性、用户行为、研究对象(是否流失客户),三个维度组合一下就得出了以下解题思路了:

  • 哪些属性的用户比较容易流失?
  • 哪些行为的用户比较容易流失?

以上两个分析思路运用的是【对比视角】,该视角下具体的分析方法有:

  • 数值型数据:均值比较
  • 分类型数据:频数分布比较(交叉分析)

以上的分析方法是统计分析,只能一个维度一个维度地去比较。但实际情况中,并不是每个维度的权重都一样的,那如何去研究各个维度的权重?

权重问题属于分类视角,故我们可以采用分类模型,要用哪个分类模型呢?不知道。可以全部采用,看模型精度得分,然后选得分最高的模型进行进一步预测。

  • Random Forest 随机森林
  • SVC 支持向量机
  • LogisticRegression 逻辑回归
  • KNN 近邻算法
  • Naive Bayes  朴素贝叶斯
  • Decision Tree 决策树
  • AdaBoost
  • GradientBoosting
  • XGB
  • CatBoost

5.分析结论及运营建议

5.1 分析结论

综合统计分析XGB算法输出特征重要性得出流失客户有以下特征(依特征重要性从大到小排列):

  1. tenure :1-5号职位的用户比较容易流失
  2. PaymentMethod :使用电子支票支付的人
  3. MonthlyCharges 、TotalCharges : 总费用在2281.92元以下,月费用在64.76元以上的客户比较容易流失
  4. PaperlessBilling : 开通电子账单
  5. Partner : 单身
  6. OnlineBackup : 没开通在线备份业务
  7. InternetService :开通了Fiber optic 光纤网络
  8. TechSupport :没开通“技术支持服务”
  9. DeviceProtection :没开通设备保护业务
  10. OnlineSecurity :没开通网络安全服务
  11. Contract :按月签订合同方式
  12. Dependents :无经济独立
  13. SeniorCitizen :青年人
  14. TotalCharges :总费用在2281.92元以下,月费用在64.76元以上的客户比较容易流失

当条件覆盖得越多,人群越精确,但与此同时,覆盖的人群也会越少。业务方可直接在数据库中,通过SQL检索符合要求的客户,然后做针对性的运营工作。

5.2 运营建议

如何留住客户,可以从两方面去思考:

  • 增加用户的沉没成本(损失厌恶)
    • 会员等级
    • 积分制
    • 充值赠送
    • 满减券
    • 其他增值服务
  • 培养用户的条件反射(习惯)
    • 会员日
    • 定期用户召回
    • 签到
    • 每日定时抽奖
    • 小游戏

电子账单解锁新权益

  • 现象:“开通电子账单”的人反而容易流失。
  • 基本假设:价格敏感型客户。电子账单,让客户理性消费。
  • 建议:让“电子账单”变成一项“福利。跟连锁便利店,联名发”商品满减券”,每月的账单时间,就将”商品满减券“和账单一起推送过去。文案:您上月消费了XX元,解锁了xx会员权益。
  • 底层规律:增加沉没成本。

“单身用户”尊享亲情网

  • 现象:“单身用户”容易流失。
  • 基本假设:社交欲望低。
  • 建议:一个单身用户拥有建立3个人以内的“亲情网”的权益。
  • 底层规律:增加沉没成本。

推广“在线备份、设备保护、技术支持、网络保护”等增值服务。

6.实战教程-数据清洗

6.1 导入模块

6.1.1 数据处理

import pandas as pd
import numpy as np

6.1.2 可视化

import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style='darkgrid',font_scale=1.3)
plt.rcParams['font.family']='SimHei'
plt.rcParams['axes.unicode_minus']=False

6.1.3 特征工程

import sklearn
from sklearn import preprocessing                            #数据预处理模块
from sklearn.preprocessing import LabelEncoder               #编码转换
from sklearn.preprocessing import StandardScaler             #归一化
from sklearn.model_selection import StratifiedShuffleSplit   #分层抽样
from sklearn.model_selection import train_test_split         #数据分区
from sklearn.decomposition import PCA                        #主成分分析 (降维)

6.1.4 分类算法

from sklearn.ensemble import RandomForestClassifier     #随机森林
from sklearn.svm import SVC,LinearSVC                   #支持向量机
from sklearn.linear_model import LogisticRegression     #逻辑回归
from sklearn.neighbors import KNeighborsClassifier      #KNN算法
from sklearn.cluster import KMeans                     #K-Means 聚类算法
from sklearn.naive_bayes import GaussianNB              #朴素贝叶斯
from sklearn.tree import DecisionTreeClassifier         #决策树

6.1.5 分类算法–集成学习

import xgboost as xgb
from xgboost import XGBClassifier                      
from catboost import CatBoostClassifier                
from sklearn.ensemble import AdaBoostClassifier        
from sklearn.ensemble import GradientBoostingClassifier 

6.1.6 模型评估

from sklearn.metrics import classification_report,precision_score,recall_score,f1_score  #分类报告
from sklearn.metrics import confusion_matrix           #混淆矩阵
from sklearn.metrics import silhouette_score           #轮廓系数(评价k-mean聚类效果)
from sklearn.model_selection import GridSearchCV       #交叉验证
from sklearn.metrics import make_scorer
from sklearn.ensemble import VotingClassifier          #投票

6.1.7 忽略警告

import warnings
warnings.filterwarnings('ignore')

6.2 读取数据

df=pd.read_csv(r'C:\Users\Think\Desktop\刻意练习数据\电信数据集\Customer-Churn.csv',header=0)
#预览数据
df.head()
#查看数据大小
df.shape
#查看数据数据及分布
df.describe()

这里安利一下spyder编辑器,下图是这个编辑器的界面。编程过程中,有赋值变量的操作,该编辑器都会在右上角呈现,双击一下,就可以像在Execel上查看数据,非常方便。查看该数据集的详情。

6.3 数据清洗

6.3.1 缺失值处理

#查看缺失值
df.isnull().sum()

注:缺失值的数据类型是 float 类型。一旦有变量的数据类型转换成float 类型,需再次查看缺失值。

6.3.2 重复值处理

#查看重复值
df.duplicated().sum()

【输出】

6.3.3 数值类型转换

#查看数据类型
df.info()

【输出】TotalCharages总费用应该跟MonthlvCharges是同一个数据类型(float64)。故需将TotalCharages由object转换成float64,且需要再次查看缺失值。

#总费用 TotalCharges  该列的数据类型应是float64,不是object
# df['TotalCharges'].astype('float64')
# 此处用“astype”转化数据类型报错 “could not convert string to float”
#改用强制转化 convert_numeric=True   
df['TotalCharges']=df['TotalCharges'].convert_objects(convert_numeric=True)
df['TotalCharges'].dtype

输出如下:再次查看缺失值:TotalCharges列有11个缺失值,处理缺失值的原则是尽量填充,最后才是删除。
缺失值填充的原则:

  • 分类型数据:众数填充
  • 数值型数据:正态分布,均值/中位数填充;偏态分布,中位数填充。

TotalCharges列是数值型数据,先画直方图查看数据分布形态。

#分别作直方图:全部客户类型、流失客户类型、留存客户类型
plt.figure(figsize=(14,5))
plt.subplot(1,3,1)
plt.title('全部客户的总付费直方图')
sns.distplot(df['TotalCharges'].dropna())

plt.subplot(1,3,2)
plt.title('流失客户的总付费直方图')
sns.distplot(df[df['Churn']=='Yes']['TotalCharges'].dropna())

plt.subplot(1,3,3)
plt.title('留存客户的总付费直方图')
sns.distplot(df[df['Churn']=='No']['TotalCharges'].dropna())

结果如下:从三个直方图看,该列数据是偏态分布,故选择中位数填充。

df.fillna({'TotalCharges':df['TotalCharges'].median()},inplace=True)
#再次确认是否还有空值
df.isnull().sum()

结果如下:

6.4 查看样本分布

研究对象’Churn’列重新编码“Yes”=1,“No”=0。重新编码有下面两种方法。

方法一:replace

df['Churn'].replace(to_replace = 'Yes', value = 1,inplace = True)
df['Churn'].replace(to_replace = 'No', value = 0,inplace = True)

方法二:map函数

df['Churn']=df['Churn'].map({'Yes':1,'No':0})

预览数据:

df['Churn'].head()

结果如下:绘制饼图,查看流失客户占比。

churn_value=df["Churn"].value_counts()
labels=df["Churn"].value_counts().index

plt.figure(figsize=(7,7))
plt.pie(churn_value,labels=labels,colors=["b","w"], explode=(0.1,0),autopct='%1.1f%%', shadow=True)
plt.title("流失客户占比高达26.5%")
plt.show()  

结果如下:【分析】:流失客户样本占比26.5%,留存客户样本占比73.5%,明显的“样本不均衡”。

解决样本不均衡有以下方法可以选择:

  • 分层抽样
  • 过抽样
  • 欠抽样

7.实战教程-特征选择

提取特征

feature=df.iloc[:,1:20]

7.1 整数编码

查看变量间的两两相关性

#重新编码
corr_df = feature.apply(lambda x: pd.factorize(x)[0])
corr_df.head()
#相关性矩阵
corr=corr_df.corr()
corr

结果如下:相关性矩阵可视化

#绘制热力图观察变量之间的相关性强弱
plt.figure(figsize=(15,12))
ax = sns.heatmap(corr, xticklabels=corr.columns, yticklabels=corr.columns, 
                 linewidths=0.2, cmap="RdYlGn",annot=True)
plt.title("Correlation between variables")

结果如下:【分析】:从热力图来看,互联网服务、网络安全、在线备份、设备维护服务、技术支持服务、开通网络电视服务、开通网络电影之间相关性很强,且是正相关。电话服务和多线业务之间也存在很强的正相关关系。

7.2 独热编码

查看研究对象”Churn”与其他变量下的标签相关性。独热编码,可以将分类变量下的标签转化成列

df_onehot = pd.get_dummies(df.iloc[:,1:21])
df_onehot.head()

结果如下:绘图查看用户流失(‘Churn’)与各个维度之间的关系

plt.figure(figsize=(15,6))
df_onehot.corr()['Churn'].sort_values(ascending=False).plot(kind='bar')
plt.title('Correlation between Churn  and variables ')

结果如下:【分析】:从图看gender(性别)、PhoneService(电话服务)相关性几乎为0,故两个维度可以忽略。[‘SeniorCitizen’,’Partner’,’Dependents’, ‘Contract’,MultipleLines,’InternetService’,  ‘OnlineSecurity’, ‘OnlineBackup’, ‘DeviceProtection’,’TechSupport’, ‘StreamingTV’, ‘StreamingMovies’,’PaperlessBilling’,’PaymentMethod’]等都有较高的相关性,将以上维度合并成一个列表kf_var,然后进行频数比较。

kf_var=list(df.columns[2:5])
for var in list(df.columns[7:18]):
    kf_var.append(var)
print('kf_var=',kf_var)

结果如下:

8.实战教程-统计分析

8.1 频数分布比较

8.1.1 卡方检验

组间有显著性差异,频数分布比较才有意义,否则可能会做无用功。
“卡方检验”,就是提高频数比较结论可信度的统计方法。

#分组间确实是有显著性差异,频数比较的结论才有可信度,故需进行”卡方检验“
from scipy.stats import chi2_contingency   #统计分析 卡方检验
#自定义卡方检验函数
def KF(x):
    df1=pd.crosstab(df['Churn'],df[x])
    li1=list(df1.iloc[0,:])
    li2=list(df1.iloc[1,:])
    kf_data=np.array([li1,li2])
    kf=chi2_contingency(kf_data)
    if kf[1]<0.05:
        print('Churn by {} 的卡方临界值是{:.2f},小于0.05,表明{}组间有显著性差异,可进行【交叉分析】'.format(x,kf[1],x),'\n')
    else:
        print('Churn by {} 的卡方临界值是{:.2f},大于0.05,表明{}组间无显著性差异,不可进行交叉分析'.format(x,kf[1],x),'\n')
#对 kf_var进行卡方检验
print('kf_var的卡方检验结果如下:','\n')
print(list(map(KF, kf_var)))

kf_var的卡方检验结果如下:

Churn by SeniorCitizen 的卡方临界值是0.00,小于0.05,表明SeniorCitizen组间有显著性差异,可进行【交叉分析】

Churn by Partner 的卡方临界值是0.00,小于0.05,表明Partner组间有显著性差异,可进行【交叉分析】

Churn by Dependents 的卡方临界值是0.00,小于0.05,表明Dependents组间有显著性差异,可进行【交叉分析】

Churn by MultipleLines 的卡方临界值是0.99,大于0.05,表明MultipleLines组间无显著性差异,不可进行交叉分析

Churn by InternetService 的卡方临界值是0.00,小于0.05,表明InternetService组间有显著性差异,可进行【交叉分析】

Churn by OnlineSecurity 的卡方临界值是0.00,小于0.05,表明OnlineSecurity组间有显著性差异,可进行【交叉分析】

Churn by OnlineBackup 的卡方临界值是0.00,小于0.05,表明OnlineBackup组间有显著性差异,可进行【交叉分析】

Churn by DeviceProtection 的卡方临界值是0.00,小于0.05,表明DeviceProtection组间有显著性差异,可进行【交叉分析】

Churn by TechSupport 的卡方临界值是0.00,小于0.05,表明TechSupport组间有显著性差异,可进行【交叉分析】

Churn by StreamingTV 的卡方临界值是0.00,小于0.05,表明StreamingTV组间有显著性差异,可进行【交叉分析】

Churn by StreamingMovies 的卡方临界值是0.00,小于0.05,表明StreamingMovies组间有显著性差异,可进行【交叉分析】

Churn by Contract 的卡方临界值是0.00,小于0.05,表明Contract组间有显著性差异,可进行【交叉分析】

Churn by PaperlessBilling 的卡方临界值是0.00,小于0.05,表明PaperlessBilling组间有显著性差异,可进行【交叉分析】

Churn by PaymentMethod 的卡方临界值是0.00,小于0.05,表明PaymentMethod组间有显著性差异,可进行【交叉分析】

从卡方检验的结果,kf_var包含的特征,组间都有显著性差异,可进行频数比较。

8.1.2 柱形图

频数比较–柱形图

plt.figure(figsize=(20,25))
a=0
for k in kf_var:
    a=a+1
    plt.subplot(4,4,a)
    plt.title('Churn BY '+ k)
    sns.countplot(x=k,hue='Churn',data=df)

结果如下:因为PaymentMethod的标签比较长,影响看图,所以单独画。

plt.xticks(rotation=45)
sns.countplot(x='PaymentMethod',hue='Churn',data=df)

可以直接从柱形图去判断对哪个维度对流失客户的影响大吗?不能,因为“样本不均衡”(流失客户样本占比26.5%,留存客户样本占比73.5%),基数不一样,故不能直接通过“频数”的柱形图去分析。
解决办法:交叉分析,且作同行百分比(’Churn’作为“行”)

8.1.3 交叉分析

print('ka_var列表中的维度与Churn交叉分析结果如下:','\n')
for i in kf_var:
    print('................Churn BY {}...............'.format(i))
    print(pd.crosstab(df['Churn'],df[i],normalize=0),'\n'#交叉分析,同行百分比

ka_var列表中的维度与Churn交叉分析结果如下:【SeniorCitizen 分析】:年轻用户 在流失、留存,两个标签的人数占比都高。【Parter 分析】:单身用户更容易流失。【Denpendents 分析】:经济不独立的用户更容易流失。【MultipleLines 分析】:是否开通MultipleLines,对留存和流失都没有明显的促进作用。【InternetService 分析】:办理了 “Fiber optic 光纤网络”的客户容易流失。【OnlineSecurity 分析】:没开通“网络安全服务”的客户容易流失。【OnlineBackup 分析】:没开通“在线备份服务”的客户容易流失。【DeviceProtection 分析】:没开通“设备保护业务”的用户比较容易流失【TechSupport 分析】:没开通“技术支持服务”的用户容易流失。【StreamingTV 分析】:是否开通“网络电视”服务,对用户留存、流失,没有明显的促进作用。【StreamingMovies 分析】:是否开通“网络电影”服务,对用户留存、流失,没有明显的促进作用。【Contract 分析】逐月签订合同的用户最容易流失。

因为”Churn BY PaymentMethod”打印出来显示不全,故我就从临时表将“交叉表”给截图出来了: 【分析】使用“电子支票”支付的人更容易流失。

8.2 均值比较

组间有显著性差异,均值比较才有意义。
显著性检验,先通过了齐性检验,再通过方差分析,最后才能做均值比较。

8.2.0 齐性检验,方差分析

#自定义齐性检验 & 方差分析 函数
def ANOVA(x):
    li_index=list(df['Churn'].value_counts().keys())
    args=[]
    for i in li_index:
        args.append(df[df['Churn']==i][x])
    w,p=stats.levene(*args)             #齐性检验
    if p<0.05:
        print('警告:Churn BY {}的P值为{:.2f},小于0.05,表明齐性检验不通过,不可作方差分析'.format(x,p),'\n')
    else:
        f,p_value=stats.f_oneway(*args) #方差分析
        print('Churn BY {} 的f值是{},p_value值是{}'.format(x,f,p_value),'\n')
        if p_value<0.05:
            print('Churn BY {}的均值有显著性差异,可进行均值比较'.format(x),'\n')
        else:
            print('Churn BY {}的均值无显著性差异,不可进行均值比较'.format(x),'\n')

对MonthlyCharges、TotalCharges维度分别进行齐性检验和方差分析

print('MonthlyCharges、TotalCharges的齐性检验 和方差分析结果如下:','\n')
ANOVA('MonthlyCharges')
ANOVA('TotalCharges')

【输出】:
MonthlyCharges、TotalCharges的齐性检验 和方差分析结果如下:

警告:Churn BY MonthlyCharges的P值为0.00,小于0.05,表明齐性检验不通过,不可作方差分析

警告:Churn BY TotalCharges的P值为0.00,小于0.05,表明齐性检验不通过,不可作方差分析

8.3 总结

用户出现以下特征比较容易流失:

  • SeniorCitizen:青年人
  • Partner :单身
  • Dependents :无经济独立
  • InternetService:开通了 “Fiber optic 光纤网络”
  • OnlineSecurity:没开通“网络安全服务”
  • OnlineBackup:没开通“在线备份业务”
  • DeviceProtection:没开通通了“设备保护业务
  • TechSupport:没开通“技术支持服务”
  • Contract:“按月”签订合同方式
  • PaperlessBilling:开通电子账单
  • PaymentMethod:使用“电子支票”支付的人

我们可以在SQL(数据库)上找有以上特征的客户,进行精准营销,即可以降低用户流失。虽然特征选得越多,越精确,但覆盖的人群也会越少。故,我们还需要计算“特征”的【重要性】,将最为重要的几个特征作为筛选条件。

计算特征的【重要性】,是“分类视角”,接下来我们会挑选常见的分类模型,进行批量训练,然后挑出得分最高的模型,进一步计算“特征重要性”。

9.实战教程-特征工程

9.1 提取特征

有前面的流失率与各个维度的相关系数柱状图可知:
流失率与gender(性别)、PhoneService(电话服务)相关性几乎为0,可以筛选掉,而customerID是随机数,不影响建模,故可以筛选掉。最终得到特征 churn_var

churn_var=df.iloc[:,2:20]
churn_var.drop("PhoneService",axis=1, inplace=True)
churn_var.head()

结果如下:

9.2 处理“量纲差异大”

“MonthlyCharges”、”TotalCharges”两个特征跟其他特征相比,量纲差异大。处理量纲差异大,有两种方法:

  1. 标准化
  2. 离散化

以上两种方法,哪个能让模型精度提高,就选哪个。根据模型的最后得分,我选了“离散化”来处理量纲差异大。

9.2.1 标准化

scaler = StandardScaler(copy=False)
scaler.fit_transform(churn_var[['MonthlyCharges','TotalCharges']])  #fit_transform拟合数据
churn_var[['MonthlyCharges','TotalCharges']]=scaler.transform(churn_var[['MonthlyCharges','TotalCharges']])  #transform标准化

print(churn_var[['MonthlyCharges','TotalCharges']].head() )#查看拟合结果

【输出】

9.2.2 特征离散化

特征离散化后,模型易于快速迭代,且模型更稳定。

1、处理’MonthlyCharges’:

#查看'MonthlyCharges'列的4分位
churn_var['MonthlyCharges'].describe() 

离散操作
18.25=<churn_var[‘MonthlyCharges’]<=35.5,标记 “1”
35.5<churn_var[‘MonthlyCharges’]<=70.35,标记 “2”
70.35<churn_var[‘MonthlyCharges’]<=89.85,标记 “3”
89.85=<churn_varf[‘MonthlyCharges’]<=118.75,标记“4”

#用四分位数进行离散
churn_var['MonthlyCharges']=pd.qcut(churn_var['MonthlyCharges'],4,labels=['1','2','3','4'])
churn_var['MonthlyCharges'].head()

结果如下:2、处理’TotalCharges’:

#查看'TotalCharges'列的4分位
churn_var['TotalCharges'].describe()

结果如下:

离散操作:
18=<churn_var[‘TotalCharges’]<=402,标记 “1”
402<churn_var[‘TotalCharges’]<=1397,标记 “2”
1397<churn_var[‘TotalCharges’]<=3786,标记 “3”
3786<churn_var[‘TotalCharges’]<=8684,标记 “4”

#用四分位数进行离散 
churn_var['TotalCharges']=pd.qcut(churn_var['TotalCharges'],4,labels=['1','2','3','4'])
churn_var['TotalCharges'].head()

【输出】

9.3 分类数据转换成“整数编码”

9.3.1 查看churn_var中分类变量的label(标签)

#自定义函数获取分类变量中的label
def Label(x):
    print(x,"--" ,churn_var[x].unique()) 
#筛选出数据类型为“object”的数据点
df_object=churn_var.select_dtypes(['object']) 
print(list(map(Label,df_object)))

结果如下:

通过同行百分比的“交叉分析”发现,label “No internetserive”的人数占比在以下特征[OnlineSecurity,OnlineBackup,DeviceProtection,TechSupport,StreamingTV,StreamingTV]都是惊人的一致,故我们可以判断label “No internetserive”不影响流失率。
因为这6项增值服务,都是需要开通“互联网服务”的基础上才享受得到的。不开通“互联网服务”视为没开通这6项增值服务,故可以将 6个特正中的“No internetserive” 并到 “No”里面。

churn_var.replace(to_replace='No internet service',value='No',inplace=True)

而特征MultipleLines的“ No phoneservice”在流失客户、留存客户样本中的人数占比几乎接近,且比较少,故可以将“ No phoneservice”并到“No”。

churn_var.replace(to_replace='No phone service',value='No',inplace=True)
df_object=churn_var.select_dtypes(['object']) 
print(list(map(Label,df_object.columns)))

结果如下:

9.3.2 整数编码

整数编码的方法有两种:

  1. sklearn中的LabelEncoder()
  2. pandas中的factorize
    此处选用 LabelEncoder()
def labelencode(x):
    churn_var[x] = LabelEncoder().fit_transform(churn_var[x])
for i in range(0,len(df_object.columns)):
    labelencode(df_object.columns[i])
print(list(map(Label,df_object.columns)))

结果如下:

9.4 处理“样本不均衡”

分拆变量

x=churn_var
y=df['Churn'].values
print('抽样前的数据特征',x.shape)
print('抽样前的数据标签',y.shape)

【输出】
抽样前的数据特征 (7043, 17)
抽样前的数据标签 (7043,)

处理样本不均衡常用的方式有三种:

  1. 分层抽样
  2. 过抽样
  3. 欠抽样

笔者先后尝试了“分层抽样”和“欠抽样”,前者最终得到的模型中精度最高的是0.63,而后者最终得到的模型中精度最低是0.78,最高是0.84。所以说“抽样方式”的选择极为重要,大家要在这里多试错。

分层抽样

sss=StratifiedShuffleSplit(n_splits=5, test_size=0.2, random_state=0)
print(sss)
print("训练数据和测试数据被分成的组数:",sss.get_n_splits(x,y))
# 分拆训练集和测试集
for train_index, test_index in sss.split(x, y):
    print("train:", train_index, "test:", test_index)
    x_train,x_test=x.iloc[train_index], x.iloc[test_index]
    y_train,y_test=y[train_index], y[test_index]

“过抽样”让模型精度更高,故我选“过抽样”。

from imblearn.over_sampling import SMOTE
model_smote=SMOTE()
x,y=model_smote.fit_sample(x,y)
x=pd.DataFrame(x,columns=churn_var.columns)
#分拆数据集:训练集 和 测试集
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.3,random_state=0)

输出数据集大小

print('过抽样数据特征:', x.shape,
      '训练数据特征:',x_train.shape,
      '测试数据特征:',x_test.shape)

print('过抽样后数据标签:', y.shape,
      '   训练数据标签:',y_train.shape,
      '   测试数据标签:',y_test.shape)

【输出】

过抽样后数据特征: (10348, 17) 训练数据特征: (7243, 17) 测试数据特征: (3105, 17)
过抽样后数据标签: (10348,)    训练数据标签: (7243,)    测试数据标签: (3105,)

10.实战教程-数据建模

使用分类算法

Classifiers=[["Random Forest",RandomForestClassifier()],
             ["Support Vector Machine",SVC()],
             ["LogisticRegression",LogisticRegression()],
             ["KNN",KNeighborsClassifier(n_neighbors=5)],
             ["Naive Bayes",GaussianNB()],
             ["Decision Tree",DecisionTreeClassifier()],
             ["AdaBoostClassifier", AdaBoostClassifier()],
             ["GradientBoostingClassifier", GradientBoostingClassifier()],
             ["XGB", XGBClassifier()],
             ["CatBoost", CatBoostClassifier(logging_level='Silent')]  
]

训练模型

Classify_result=[]
names=[]
prediction=[]
for name,classifier in Classifiers:
    classifier=classifier
    classifier.fit(x_train,y_train)
    y_pred=classifier.predict(x_test)
    recall=recall_score(y_test,y_pred)
    precision=precision_score(y_test,y_pred)
    f1score = f1_score(y_test, y_pred)
    class_eva=pd.DataFrame([recall,precision,f1score])
    Classify_result.append(class_eva)
    name=pd.Series(name)
    names.append(name)
    y_pred=pd.Series(y_pred)
    prediction.append(y_pred)

11.模型评估

names=pd.DataFrame(names)
names=names[0].tolist()
result=pd.concat(Classify_result,axis=1)
result.columns=names
result.index=["recall","precision","f1score"]
result

【输出】
特征工程,采用“标准化”处理量纲差异,采用“分层抽样”处理样本不均衡。
最终模型精度得分,最高分是0.63,是“朴素贝叶斯”模型

特征工程,采用“离散化”处理量纲差异,采用“过抽样”处理样本不均衡。
最终模型精度得分,最高分是0.84,是“XGB”模型

12.基于“XGB”模型输出特征重要性

笔者尝试了两个算法分别输出“特征重要性”:CatBoost算法 和 XGB 算法

  • CatBoost算法
model = CatBoostClassifier()
model.fit(x_train,y_train,eval_set=(x_test, y_test),plot=True)
#特征重要性可视化
catboost=pd.DataFrame(columns=['feature','feature_importance'])
catboost['feature']=model.feature_names_
catboost['feature_importance']=model.feature_importances_
catboost=catboost.sort_values('feature_importance',ascending=False#降序排列
plt.figure(figsize=(10,10))
plt.title('特征重要性')
sns.barplot(x='feature_importance',y='feature',data=catboost)

【输出】-XGB 算法

model_xgb= XGBClassifier()
model_xgb.fit(x_train,y_train)
from xgboost import plot_importance
plot_importance(model_xgb,height=0.5)
plt.show()

【输出】由于 XGB算法精度得分最高,故我们以XGB得到的“特征重要性”进行分析。
【分析】

  1. 第一重要特征:tenure
plt.figure(figsize=(20,4))
sns.countplot(x='tenure',hue='Churn',data=df)

【输出】【分析】
由图可知,流失客户集中在1-5号职位,运营团队需要重点关注1-5号职位。

  1. 第二重要特征:PaymentMethod

【分析】
使用“电子支票”支付的人更容易流失。

  1. 第三重要特征:MonthlyCharges
    查看流失用户、留存用户在付费方面的偏好:
    ‘MonthlyCharges’、’TotalCharges’,离散化后,可进行卡方检验,然后交叉分析。
  • 卡方检验:’MonthlyCharges’、’TotalCharges’
df['MonthlyCharges-']=churn_var['MonthlyCharges']
df['TotalCharges-']=churn_var['TotalCharges']
print('kf_var的卡方检验结果如下:','\n')
KF('MonthlyCharges-')
KF('TotalCharges-')

【输出】
kf_var的卡方检验结果如下:

Churn by MonthlyCharges 的卡方临界值是0.00,小于0.05,表明MonthlyCharges组间有显著性差异,可进行【交叉分析】

Churn by TotalCharges 的卡方临界值是0.00,小于0.05,表明TotalCharges组间有显著性差异,可进行【交叉分析】

  • 交叉分析
for i in ['MonthlyCharges','TotalCharges']:
    print('................Churn BY {}...............'.format(i))
    print(pd.crosstab(df['Churn'],df[i],normalize=0),'\n')

【输出】18.25=<churn_var[‘MonthlyCharges’]<=35.5,标记 “1”
35.5<churn_var[‘MonthlyCharges’]<=70.35,标记 “2”
70.35<churn_var[‘MonthlyCharges’]<=89.85,标记 “3”
89.85=<churn_varf[‘MonthlyCharges’]<=118.75,标记“4”
【分析】
月付费70.35–118.75元的用户更容易流失18=<churn_var[‘TotalCharges’]<=402,标记 “1”
402<churn_var[‘TotalCharges’]<=1397,标记 “2”
1397<churn_var[‘TotalCharges’]<=3786,标记 “3”
3786<churn_var[‘TotalCharges’]<=8684,标记 “4”
【分析】
总付费18–1397元的用户更容易流失

基于”MonthlyCharges”和“TotalCharges”画四分图:
求两个维度的均值

 print('MonthlyCharges的均值是{:.2f},TotalCharges的均值是{:.2f}'.format(df['MonthlyCharges'].mean(),df['TotalCharges'].mean()))

流失客户四分图:

df_1=df[df['Churn']==1#流失客户
df_0=df[df['Churn']==0#留存客户
plt.figure(figsize=(10,10))   
sns.scatterplot('MonthlyCharges','TotalCharges',hue='Churn', palette=plt.cm.RdYlBu,data=df_1)
plt.axhline(y=df['TotalCharges'].mean(),ls="-",c="k")
plt.axvline(x=df['MonthlyCharges'].mean(),ls="-",c="green")

【输出】【分析】
四分图的右下区域,流失客户比较集中,即总费用在2281.92元以下,月费用在64.76元以上的客户比较容易流失。

留存客户四分图

plt.figure(figsize=(10,10)) 
sns.scatterplot('MonthlyCharges','TotalCharges',hue='Churn', palette=plt.cm.RdYlBu_r,data=df_0)
plt.axhline(y=df['TotalCharges'].mean(),ls="-",c="k")
plt.axvline(x=df['MonthlyCharges'].mean(),ls="-",c="green")

【输出】【结论】
综合“ 统计分析” 和 “XGB算法输出特征重要性” 得出流失客户有以下特征(依特征重要性从大到小排列):

  1. tenure:1-5号职位的用户比较容易流失
  2. PaymentMethod:使用“电子支票”支付的人
  3. MonthlyCharges 、TotalCharges:总费用在2281.92元以下,月费用在64.76元以上的客户比较容易流失
  4. PaperlessBilling:开通电子账单
  5. Partner:单身
  6. OnlineBackup:没开通“在线备份业务”
  7. InternetService:开通了 “Fiber optic 光纤网络”
  8. TechSupport:没开通“技术支持服务”
  9. DeviceProtection:没开通通了“设备保护业务
  10. OnlineSecurity:没开通“网络安全服务”
  11. Contract:“按月”签订合同方式
  12. Dependents:无经济独立
  13. SeniorCitizen :青年人
  14. TotalCharges:总费用在2281.92元以下,月费用在64.76元以上的客户比较容易流失

转自猫有九条命。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Prometheus 实战教程 + Grafana + Python — 实时监控东方财富人气榜股票

上次我们讲过普罗米修斯(prometheus)这个接近完美的监控系统,有很多读者不了解它到底要如何搭建、应用,需要一篇 Prometheus 实战教程。今天我们就结合普罗米修斯、Grafana和Python采集脚本,写一个小小的东方财富人气榜 TOP100监控系统。

跟着本文的教程耐心往下走,你可能只需要花30分钟便可完成环境的搭建,非常舒服,下面先介绍基本概念。

普罗米修斯(prometheus)上次我们已经使用一整篇文章介绍过了,它是一个开源监控报警系统和时序列数据库。如果你没有阅读过这篇文章,请花五分钟读一下:

Grafana 是一个开源的数据可视化网络应用程序平台。用户配置连接的数据源之后,Grafana可以在网络浏览器里显示数据图表和警告。

比如说我基于 普罗米修斯(prometheus) + node_exporter 监控主机性能指标,然后由Grafana构建主机实时监控仪表盘,它是长这样的:

至于东方财富人气榜,指的是这个:

它能将市场目前最活跃的一些股票提取出来,可供我们作为投资的一种参考。

而我们今天要做的,就是自己搭建一套监控系统,实时监控某只股票在TOP100上的排名变化。

1.Prometheus 安装教程

创建 Prometheus 安装目录并添加 promethus 用户:

PROM_PATH='/data/prometheus'
mkdir -p ${PROM_PATH}
mkdir -p ${PROM_PATH}/{data,conf,logs,bin}
useradd prometheus
cd /usr/local/src

下载解压 prometheus, 这里我们选用2021年5月18日更新的最新版 v2.27.1:

wget https://github.com/prometheus/prometheus/releases/download/v2.27.1/prometheus-2.27.1.linux-amd64.tar.gz
tar -xvf prometheus-2.27.1.linux-amd64.tar.gz
cd prometheus-2.27.1.linux-amd64/
cp prometheus promtool ${PROM_PATH}/bin/
cp prometheus.yml ${PROM_PATH}/conf/
chown -R prometheus.prometheus /data/prometheus

设置环境变量:

cat >> /etc/profile <<EOF
PATH=/data/prometheus/bin:$PATH:$HOME/bin
EOF

将 Promethus 配置为系统服务之一,以便使用 systemctl 命令管控服务:

cat >>/etc/systemd/system/prometheus.service <<EOF
[Unit]
Description=Prometheus
Documentation=https://prometheus.io/
After=network.target

[Service]
Type=simple
User=prometheus
ExecStart=/data/prometheus/bin/prometheus --config.file=/data/prometheus/conf/prometheus.yml --storage.tsdb.path=/data/prometheus/data --storage.tsdb.retention=90d
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

现在使用下面的systemctl命令重新加载systemd系统,并查看服务是否启动:

systemctl daemon-reload
systemctl enable prometheus
systemctl start prometheus
systemctl status prometheus

看到 running 状态说明一切正常:

记得开放9090端口,这样才可以访问 Prometheus 的 Web 端,访问 http://服务器IP:9090 查看得到 Prometheus Web界面,说明安装成功:

2.Grafana 安装教程

Grafana 我们也使用最新的 8.0.1 版本,安装方式如下:

CentOS系列系统使用以下命令安装:

cd /usr/local/src
wget https://dl.grafana.com/oss/release/grafana-8.0.1-1.x86_64.rpm
sudo yum localinstall grafana-6.5.2-1.x86_64.rpm

Ubuntu和Debian系列系统使用以下命令安装:

cd /usr/local/src
sudo apt-get install -y adduser libfontconfig1
wget https://dl.grafana.com/oss/release/grafana_8.0.1_amd64.deb
sudo dpkg -i grafana_8.0.1_amd64.deb

然后启动系统服务即可:

systemctl start grafana-server
systemctl status grafana-server

看到 running 状态说明一切正常:

记得开放3000端口,这样你才可以访问你的Grafana: http://你的服务器IP:3000 如下所示:

输入用户名,密码登录系统。用户名与密码都是”admin”,如果能打开页面则已经安装成功了。

3.初尝Grafana+Prometheus实战教程

为了初步尝试这套系统,我们可以通过简单的采集主机性能数据开始。Node_exporter是一个Prometheus推出的官方主机性能采集工具。通过它我们能很方便地输出主机性能指标到Prometheus.

3.1 下载安装Node_Exporter:

NODE_PATH='/data/prometheus/node_exporter/'
cd /usr/local/src/
mkdir -p ${NODE_PATH}
wget https://github.com/prometheus/node_exporter/releases/download/v1.1.2/node_exporter-1.1.2.linux-amd64.tar.gz
tar -xvf node_exporter-1.1.2.linux-amd64.tar.gz
cp node_exporter-1.1.2.linux-amd64/node_exporter ${NODE_PATH}
chown -R prometheus.prometheus ${NODE_PATH}

配置node_exporter为系统服务:

cat > /lib/systemd/system/node_exporter.service <<EOF
[Unit]
Description=node_exporter
Documentation=https://prometheus.io/
After=network.target
 
[Service]
Type=simple
User=prometheus
ExecStart=/data/prometheus/node_exporter/node_exporter
Restart=on-failure
 
[Install]
WantedBy=multi-user.target
EOF

现在使用systemctl命令重新加载系统命令,并查看服务是否启动:

systemctl daemon-reload
systemctl enable node_exporter
systemctl start node_exporter
systemctl status node_exporter

看到如下图的状态说明启动成功。

放行9100端口,访问http://你的服务器地址:9100/metrics 看到如下指标页面说明安装成功:

配置 prometheus.yaml (ubuntu 下为 prometheus.yml), 让 prometheus 采集 node_exporter 输出的指标数据:

vim /data/prometheus/conf/prometheus.yml

配置如下:

# my global config
global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: 'prometheus'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
    - targets: ['localhost:9090']

   # 主要是新增了node_exporter的job,如果有多个node_exporter,在targets数组后面加即可

  - job_name: 'node_exporter'
    static_configs:
      - targets: ['localhost:9100']

保存后重启prometheus:

systemctl restart prometheus

最后配置Grafana:

然后选择 Prometheus 数据源:

输入 Prometheus url 然后点击 save&test 保存:

然后导入官方仪表盘,官方提供的模板号为8919:

然后你就能看见本机非常漂亮的性能指标数据仪表盘了。

不看不知道,一看吓一跳,看来我需要升级这台机器的内存了。

4.编写采集脚本

为了能够采集东方财富人气榜前100名,我们需要用Python编写一个人气榜采集脚本,并使其像 node_exporter 一样输出指标信息:

为了达到这个目的,我们必须安装 prometheus_client 模块:

pip3 install prometheus_client

获取股票排名的代码如下:

# Python实用宝典
# 2021-06-13
# 文件名: fetch_stock.py
import time
import requests
from prometheus_client import start_http_server, CollectorRegistry, Gauge


reg = CollectorRegistry()
gauge = Gauge(
    'rank', '人气榜排名',
    ['stock_id'], registry=reg
)


def process_request():
    url = "https://emappdata.eastmoney.com/stockrank/getAllCurrentList"
    kwargs = {
        "appId": "appId01",
        "pageNo": 1,
        "pageSize": "100",
    }
    result = requests.post(url, json=kwargs).json()
    for i in result.get("data", []):
        gauge.labels(stock_id=i["sc"]).set(i["rk"])
    time.sleep(60)


if __name__ == '__main__':
    start_http_server(8000, registry=reg)
    while True:
        process_request()

这里我们只捕获人气榜前100名,并通过Prometheus客户端的start_http_server开启一个Web服务,这样你通过http服务访问8000端口的时候就能输出这些指标。

为了让其能持续输出指标数据,我们要用nohup使其成为一个常驻进程:

nohup python3 fetch_stock.py &

开放8000端口,访问 http://你的服务器IP:8000 就能查看输出的指标:

5.应用采集脚本

同配置Node_exporter一样,我们需要将自己编写好的采集脚本落入Prometheus,配置prometheus.yaml:

配置 prometheus.yaml, 让 prometheus 采集 node_exporter 输出的指标数据:

#(CentOS) vim /data/prometheus/conf/prometheus.yaml
vim /data/prometheus/conf/prometheus.yml # ubuntu

配置如下:

# my global config
global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: 'prometheus'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
    - targets: ['localhost:9090']

   # 主要是新增了node_exporter的job,如果有多个node_exporter,在targets数组后面加即可
  - job_name: 'node_exporter'
    static_configs:
      - targets: ['localhost:9100']

   # 新增我们的Python股票采集脚本
  - job_name: 'hot_list'
    static_configs:
      - targets: ['localhost:8000']

保存后重启prometheus:

systemctl restart prometheus

最后配置Grafana, 选择新建一个dashboard:

然后选择rank指标:

点击 Use query 就能获取所有股票的排名曲线:

6.配置Grafana告警

为了在某只股票达到某种排名的时候触发通知,我们需要先配置好告警渠道:

然后配置邮件告警,点击 Test, 此时 Grafana 会告诉你一个错误:

就是我们还没有配置好 SMTP 相关服务,需要配置 SMTP 相关服务才能正常发送邮件,如果你是按照本文按照Grafana的教程走下来的,那么Grafana.ini的文件位于 /etc/grafana/grafana.ini.

vim /etc/grafana/grafana.ini

然后在 smtp 部分配置你的 host、user、password、from_address、from_name,并打开 enabled 如下图所示:

然后重启 Grafana-server

systemctl restart grafana-server

再点击Test,你的邮箱里收到这样的邮件说明通知可以正常发送了:

然后我们进入正题,监控某只股票的排名变化,比如 SH600070:

然后点击 Alert 配置告警,一旦其排名高于65名则发送邮件通知:

完成后点击右上角的 save 保存即可:

然后进入 Alerting 告警中心,你会看到刚刚配置的告警规则在这里可以进行管控:

点击Pause可以暂停这个告警,Edit alert可以去更改告警条件。

一旦触发告警,这个状态便会更改,你就会收到邮件:

邮件效果如下:

邮件里的告警图片没显示出来,因为我们没有安装 “grafana image renderer”, 需要在你的服务器执行以下命令安装并重启 Grafana:

grafana-cli plugins install grafana-image-renderer
systemctl restart grafana-server

新的告警邮件便能看到图片了:

怎么样,用Prometheus+Grafana+Python采集搭建一个股票监控系统还是非常简单的吧?创新性地监控东方财富人气榜上某只股票的变化并产生告警,能让你熟悉监控策略的配置,见微知著。跟着本文的教程走,相信你会有不少收获。

如果我们延伸一下,结合量化投资系列教程的可转债交易策略 — Python 量化投资实战教程(10),是否可以构建一些更有意义的策略?答案是肯定的。

我们可以监控所有100元以下的可转债对应的股票,如果这些股票进入了人气榜TOP100或者飙升榜(本文没有采集,有兴趣的读者可以自行采集),就购入这些低价可转债,这种买入策略或许也不错。

你也可以抛弃东方财富的榜单分类,构建自己的排名环比增长买入策略,环比下跌卖出策略,我相信这会非常有意思。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Pandas实战教程:将Excel转为html格式

大家谈及用Pandas导出数据,应该就会想到to.xxx系列的函数。

这其中呢,比较常用的就是pd.to_csv()pd.to_excel()。但其实还可以将其导成Html网页格式,这里用到的函数就是pd.to_html()

读取Excel

今天我们要实现Excel转为html格式,首先需要用读取Excel中的表格数据。

import pandas as pd
data = pd.read_excel('测试.xlsx')

查看数据

data.head()

下面我们来学习把DataFrame转换成HTML表格的方法。

生成Html

to_html()函数可以直接把DataFrame转换成HTML表格,只需一行代码即可实现:

html_table = data.to_html('测试.html')

运行上面代码后,工作目录中多了测试.html文件,使用网页浏览器打开它,显示内容如下👇

print(data.to_html())

通过print打印,可以看到DataFrame的内部结构被自动转换为嵌入在表格中的<TH>,<TR>,<TD>标签,保留所有内部层级结构。

调整格式

我们还可以自定义修改参数,来调整生成HTML的格式。

html_table = data.to_html('测试.html',header = True,index = False,justify='center')

再次打开新生成的测试.html文件,发现格式已经发生了变化。

如果想对格式进行进一步调整(增加标题、修改颜色等),就需要一些HTML知识了,可以对生成的测试.html文件中的文本进行调整。

对于有些小伙伴可能需要进行页面展示,就要搭配Flask库来使用了。

小结

Pandas提供read_html()to_html()两个函数用于读写html格式的文件。这两个函数非常有用,一个轻松将DataFrame等复杂的数据结构转换成HTML表格;另一个不用复杂爬虫,简单几行代码即可抓取Table表格型数据,简直是个神器!

今天篇幅很短,主要讲了Pandas中to_html()这个函数。使用该函数最大的优点是:我们在不了解html知识的情况下,就能生成一个表格型的HTML。本文转自快学Python

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

FoolNLTK — 简单好用的中文NLP工具包

FoolNLTK — 作者号称“可能不是最快的开源中文分词,但很可能是最准的开源中文分词”。

这个开源工具包基于BiLSTM模型训练而成,功能包含分词,词性标注,实体识别。并支持用户自定义词典,可训练自己的模型及批量处理文本。

1.准备

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 进行安装。

(可选1) 如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.

(可选2) 此外,推荐大家用VSCode编辑器来编写小型Python项目:Python 编程的最好搭档—VSCode 详细指南

Windows环境下打开Cmd(开始—运行—CMD),苹果系统环境下请打开Terminal(command+空格输入Terminal),输入命令安装依赖:

pip install foolnltk

2.使用说明

2.1 分词功能

通过 fool.cut 函数,能够实现分词功能:

import fool

text = "一个傻子在北京"
print(fool.cut(text))
# ['一个', '傻子', '在', '北京']

命令行针对文件进行分词操作:

python -m fool [filename]

2.2 用户自定义词典

词典格式格式如下,词的权重越高,词的长度越长就越越可能出现,权重值请大于1:

难受香菇 10
什么鬼 10
分词工具 10
北京 10
北京天安门 10

加载词典:

import fool
fool.load_userdict(path) # path 为词典路径
text = ["我在北京天安门看你难受香菇", "我在北京晒太阳你在非洲看雪"]
print(fool.cut(text))
#[['我', '在', '北京', '天安门', '看', '你', '难受', '香菇'],
# ['我', '在', '北京', '晒太阳', '你', '在', '非洲', '看', '雪']]

删除词典:

fool.delete_userdict();

2.3 词性标注

词性标注只需要使用 pos_cut 函数,生成的数组结果中,第一个维度是对应字符串的识别结果。第二个维度是分词后的每个词语及对应的词性。

import fool

text = ["一个傻子在北京"]
print(fool.pos_cut(text))
#[[('一个', 'm'), ('傻子', 'n'), ('在', 'p'), ('北京', 'ns')]]

2.4 实体识别

实体识别的结果元素中,第一二个元素是关键词的起始坐标和结束坐标,第三个元素是实体类别,最后一个元素是实体关键词。

import fool 

text = ["一个傻子在北京","你好啊"]
words, ners = fool.analysis(text)
print(ners)
#[[(5, 8, 'location', '北京')]]

3.定制自己的模型

你可以在 linux 的 Python3 环境定制自己的模型。

git clone https://github.com/rockyzhengwu/FoolNLTK.git
cd FoolNLTK/train

1.训练。模型训练 data_dir 存放训练数据格式如 datasets/demo 下。下载与训练的模型,我这里是将下载的模型软链接到 pretrainmodel 下

python ./train_bert_ner.py --data_dir=data/bid_train_data \
  --bert_config_file=./pretrainmodel/bert_config.json \
  --init_checkpoint=./pretrainmodel/bert_model.ckpt \
  --vocab_file=./pretrainmodel/vocab.txt \
  --output_dir=./output/all_bid_result_dir/ --do_train

2.导出模型。模型导出 predict 同时指定 do_export 就能导出 pb 格式的模型,用于部署:

python ./train_bert_ner.py --data_dir=data/bid_train_data \
  --bert_config_file=./pretrainmodel/bert_config.json \
  --init_checkpoint=./pretrainmodel/bert_model.ckpt \
  --vocab_file=vocab.txt \
  --output_dir=./output/all_bid_result_dir/ --do_predict --do_export

3.预测。在 bert_predict.py 中指定下面三个参数就能加载训练好的模型完成预测:

VOCAB_FILE = './pretrainmodel/vocab.txt'
LABEL_FILE = './output/label2id.pkl'
EXPORT_PATH = './export_models/1581318324'

如果你有兴趣构建自己的模型,并且还有一些构建的疑惑,你可以在这里看到详细的说明文档:
https://github.com/rockyzhengwu/FoolNLTK/blob/master/train/README.md

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python 如何找到下一个“游戏驿站”?

Reddit Hyped Stocks — 是GitHub上开源的一个查找 Reddit 当前被炒作的股票的Web应用程序。

通过它,你或许能找到下一支“游戏驿站”。

1.怎么判断“炒作”?

作者使用“炒作得分”的概念对Reddit上所有被炒作的股票进行了排序,其中炒作得分的计算如下:

收集的原始数据包含所选子Reddit的前n个帖子,每次都会收集包括点赞在内的所有基本数据。

每个帖子都会被标记为某只股票的炒作贴(基于标题)。

然后建立一个矩阵,其中每一行代表一个时间点,每一列代表一只股票。矩阵的值表示在某个时间点,一只股票的所有帖子的炒作分数的总和。

然后计算每个时间点的差值作为增量值,比如我想知道过去7天的炒作分数排行,我会对各个股票将过去7天的增量值之和作为总和来计算炒作分数并进行排行。

有关确切的算法,请查看源代码中的 ticker_score_calulation.py

2.功能与说明

炒作图(顶部),即下图所示:

该图表显示了Reddit上当前炒作分数最高的一些股票,显示了排名前15位的股票的累计炒作得分。默认情况下,这个图表显示过去一星期内各个股票的分数变化。

炒作表(左侧),如下图所示:

这里会显示排名前30位的炒作股票及其各自的炒作得分,以下值:

  • Score Abs:所有相关帖子的炒作分数总和
  • Δ7d/Δ3d/Δ1d:周期分别为7/3/1天的增量炒作得分

另外,每一行都指示一天的仓位增/减(两个上/下箭头表示+/- 5个排名,一个上/下箭头表示小于+/- 5个排名,= 表示不变)。

单击股票名字后,会打开详细信息视图:

这里会展示股票的基本信息,每股收益、所属行业、PE值、关联的Reddit帖子等等。

3.安装部署

在Python实用宝典后台回复:Reddit 可以获取此开源代码库的代码和数据(reddit-hyped-stocks 及 data.db)。

(选项1)使用Docker运行应用程序:

(选项2)在没有Docker的情况下运行应用程序/设置开发环境

  • 1. 将存储库克隆到本地计算机
  • 2. 安装Python 3和Node.js
  • 3. cd 到 backend 并运行 pip3 install -r requirements.txt 以安装后端依赖项。运行 backend/start.sh 以运行后端开发服务器
  • 4. cd到 frontend 并运行 npm i 以安装前端依赖项。运行npm run start以运行前端开发服务器。

(非必须)收集原始数据

由于炒作得分取决于帖子,因此必须定期(例如每小时)爬取Reddit帖子数据。

必须条件:获取 Reddit API token:

  • 1. 前往 https://www.reddit.com/prefs/apps
  • 2. 点击“创建应用”并填写信息,使用“script”类型
  • 3. 将密钥和应用程序ID复制到文件中:  backend/praw.ini

默认情况下,子论坛 robinhoodpennystockspennystocks 会被爬取(可在中配置backend/load_data.py)。

数据会被保存到Sqlite数据库中。要查询数据,请使用脚本 backend/load_data.py 或运行 ./run-load-data-docker.sh

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Newspaper — 一个能下载38种语言新闻文章的 Python 模块

Newspaper 是一个很棒的python库,用于提取和整理文章。

它有以下的优点:

  • 多线程文章下载框架
  • 识别新闻网址
  • 从html提取文本
  • 从html提取顶部图像
  • 从html提取所有图像
  • 从文本中提取关键字
  • 自动提取摘要
  • 自动提取作者
  • 自动提取 Google 趋势词

下面是这个开源模块的安装和使用教程。

1.准备

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 进行安装。

(可选1) 如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.

(可选2) 此外,推荐大家用VSCode编辑器来编写小型Python项目:Python 编程的最好搭档—VSCode 详细指南

Windows环境下打开Cmd(开始—运行—CMD),苹果系统环境下请打开Terminal(command+空格输入Terminal),输入命令安装依赖:

pip3 install newspaper3k

遇到任何安装问题,可以在本文下方留言框或Python实用宝典公众号上留言,也可以访问项目官网查看相关安装指南:
https://github.com/codelucas/newspaper

2.基本使用

Newspaper 中是以文章为对象实现各种操作的,比如下载指定新闻的HTML:

from newspaper import Article

url = 'http://fox13now.com/2013/12/30/new-year-new-laws-obamacare-pot-guns-and-drones/'

# 根据url生成Article对象
article = Article(url)

# 下载文章
article.download()

# 文章的HTML
article.html
#'<!DOCTYPE HTML><html itemscope itemtype="http://...'

通过解析新闻和文章,你能获得此文章的作者、发布时间、摘要、顶部图像、所有图像、多媒体等:

"""
Python 实用宝典
《Newspaper — 一个能下载38种语言新闻文章的 Python 模块》
"""

# 解析文章
article.parse()

# 获取文章作者
article.authors
# ['Leigh Ann Caldwell', 'John Honway']

# 获取文章发布日期
article.publish_date
# datetime.datetime(2013, 12, 30, 0, 0)

# 获取文章文本
article.text
# 'Washington (CNN) -- Not everyone subscribes to a New Year's resolution...'

# 获取顶部图像
article.top_image
# 'http://someCDN.com/blah/blah/blah/file.png'

# 获取文章多媒体资源
article.movies
# ['http://youtube.com/path/to/link.com', ...]

除此之外,该模块还附带了 NLP 功能,你能用它来识别文章关键字并自动提取摘要:

# 使用 NLP 解析
article.nlp()

# 获取文章关键词
article.keywords
# ['New Years', 'resolution', ...]

# 获取文章摘要
article.summary
# 'The study shows that 93% of people ...'

你看,这个工具不无敌吗?它还能提取某个网站的所有新闻文章,比如我想提取CNN的新闻文章:

import newspaper

cnn_paper = newspaper.build('http://cnn.com')

for article in cnn_paper.articles:
    print(article.url)
# http://www.cnn.com/2013/11/27/justice/tucson-arizona-captive-girls/
# http://www.cnn.com/2013/12/11/us/texas-teen-dwi-wreck/index.html

在此之上,你还能拿到CNN的其他新闻门户分类:

for category in cnn_paper.category_urls():
    print(category)

# http://lifestyle.cnn.com
# http://cnn.com/world
# http://tech.cnn.com
# ...

许多中文媒体的文章下载也是支持的:

import newspaper
sina_paper = newspaper.build('http://www.sina.com.cn/', language='zh')

for category in sina_paper.category_urls():
    print(category)
# http://health.sina.com.cn
# http://eladies.sina.com.cn
# http://english.sina.com
# ...

article = sina_paper.articles[0]
article.download()
article.parse()

print(article.text)
# 新浪武汉汽车综合 随着汽车市场的日趋成熟,
# 传统的"集全家之力抱得爱车归"的全额购车模式已然过时,
# 另一种轻松的新兴 车模式――金融购车正逐步成为时下消费者购
# 买爱车最为时尚的消费理念,他们认为,这种新颖的购车
# 模式既能在短期内
# ...

print(article.title)
# 两年双免0手续0利率 科鲁兹掀背金融轻松购_武汉车市_武汉汽
# 车网_新浪汽车_新浪网

从上面的例子你可以看到,你可以非常容易地提取中文文章,仅需要在Article的language参数中指定 ‘zh’ :

"""
Python 实用宝典
《Newspaper — 一个能下载38种语言新闻文章的 Python 模块》
"""

from newspaper import Article
url = 'http://www.bbc.co.uk/zhongwen/simp/chinese_news/2012/12/121210_hongkong_politics.shtml'
a = Article(url, language='zh') # Chinese
a.download()
a.parse()
print(a.text[:150])

# 香港行政长官梁振英在各方压力下就其大宅的违章建
# 筑(僭建)问题到立法会接受质询,并向香港民众道歉。
# 梁振英在星期二(12月10日)的答问大会开始之际
# 在其演说中道歉,但强调他在违章建筑问题上没有隐瞒的
# 意图和动机。 一些亲北京阵营议员欢迎梁振英道歉,
# 且认为应能获得香港民众接受,但这些议员也质问梁振英有

print(a.title)
# 港特首梁振英就住宅违建事件道歉

这个工具所支持的所有语言如下:

input code      full name

  ar              Arabic
  be              Belarusian
  bg              Bulgarian
  da              Danish
  de              German
  el              Greek
  en              English
  es              Spanish
  et              Estonian
  fa              Persian
  fi              Finnish
  fr              French
  he              Hebrew
  hi              Hindi
  hr              Croatian
  hu              Hungarian
  id              Indonesian
  it              Italian
  ja              Japanese
  ko              Korean
  lt              Lithuanian
  mk              Macedonian
  nb              Norwegian (Bokmål)
  nl              Dutch
  no              Norwegian
  pl              Polish
  pt              Portuguese
  ro              Romanian
  ru              Russian
  sl              Slovenian
  sr              Serbian
  sv              Swedish
  sw              Swahili
  th              Thai
  tr              Turkish
  uk              Ukrainian
  vi              Vietnamese
  zh              Chinese

你可以按需选择自己所需要的语言。

3.高级玩法

前面我们说过,Newspaper 是一个可以并发下载文章的框架,它是这么玩的:

"""
Python 实用宝典
《Newspaper — 一个能下载38种语言新闻文章的 Python 模块》
"""

import newspaper
from newspaper import news_pool

slate_paper = newspaper.build('http://slate.com')
tc_paper = newspaper.build('http://techcrunch.com')
espn_paper = newspaper.build('http://espn.com')

papers = [slate_paper, tc_paper, espn_paper]
news_pool.set(papers, threads_per_source=2) # (3*2) = 总计 6 线程
news_pool.join()

# 到这一步,你可以假定三个新闻源的文章都下载完成了
print(slate_paper.articles[10].html)
# u'<html> ...'

可以看到,作者通过 build 三个新闻源,拿到一个总的新闻源池进行并发请求。

其中,.set 函数起到了调度作用,它能通过指定 threads_per_source 的值设定每个新闻源的线程。最后再 join 起来开始并发请求新闻源并开始下载新闻。

此外,Newspaper 还有一些参数可供你配置,比如:

keep_article_html,默认为False,“如果要保留正文文本的html,则设置为True”

http_success_only,默认为True,“设置为False也可以捕获非2XX响应”

MIN_WORD_COUNT,默认为300,“文章中的单词数量”

MIN_SENT_COUNT,默认为7,“句子数”

MAX_TITLE,默认值为200,“文章标题中的字符数”

MAX_TEXT,默认值为100000,“文章文字中的字符数”

MAX_KEYWORDS,默认值为35,“文章中的关键词数”

MAX_AUTHORS,默认值为10,“文章中的作者姓名数量”

MAX_SUMMARY,默认值为5000,“摘要的字符数”

MAX_SUMMARY_SENT,默认为5,“摘要中的句子数”

memoize_articles,默认为True,“运行后缓存并保存运行后的文章”

fetch_images,默认为True,“如果不需要获取图片,请将其设置为false”

request_timeout,默认为7,请求7秒后未响应完成则超时

number_threads,默认值为10,多线程数量

如果你需要使用以上参数,可以设一个Config对象,传入指定的 Article 对象或build 方法中,如:

import newspaper
from newspaper import Config, Article, Source

config = Config()
config.memoize_articles = False

cbs_paper = newspaper.build('http://cbs.com', config)

非常简单易懂,而且设置起来的维护成本不算很高。

在做一些舆情分析或者NLP算法训练/测试的时候,这个模块简直就是你的福音。你可以很方便地从网站上提取任意语言的文本数据,拿来测试或者训练都可以。

对于那些想要搞舆情分析,寻找市场热点的同学而言,这个模块也是非常方便,你能搭配邮件发布工具,并使用Newspaper的关键词提取功能,迅速制作一个关键词热点实时告警的工具。

总而言之,这是一个非常值得了解并学习使用的第三方模块,强烈推荐。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典