背景

NumPy(Numerical Python)诞生已经过去了 15 年,前一段时间NumPy 核心开发团队的论文终于发表,详细介绍了使用 NumPy 的数组编程(Array programming),并且登上了Nature 。

NumPy 是什么?它是大名鼎鼎的使用 Python 进行科学计算的基础软件包,是 Python 生态系统中数据分析、机器学习、科学计算的主力军,极大简化了向量与矩阵的操作处理。

  • 功能强大的 N 维数组对象
  • 精密广播功能函数
  • 集成 C/C++ 和 Fortran 代码的工具
  • 强大的线性代数、傅立叶变换和随机数功能

在平时数据处理中,大部分人用的都是Pandas,用Numpy的场景可能比较少,但是Pandas是基于Numpy实现的更高级的库,使大家用起来更方便。但在做深度学习时用Numpy比较多,比如:图像处理,图片里面其实都是Numpy数组;音频处理;文本处理等等。

下面为大家介绍一些Numpy的常用基础

Numpy基础

  • 安装
    由于Numpy是第三方库,默认是不集成在Python里面,所以就需要手动安装一下: 如果你安装的是Anaconda,那么就不用再安装了,请忽略 如果你是从官方网站下载的Python,那么你就需要手动安装一下这个库
#指定阿里云镜像,安装更快
pip install numpy -i https://mirrors.aliyun.com/pypi/simple/   
  • 导入
    默认成规,numpy导入后命名为 np,所以在python脚本(程序)里面看见np一般都是代表numpy
import numpy as np   
  • 认识Ndarray
    计算机里面能计算的就是数字,也就是数学里面的各种数字,我们都知道数学里面的数组可以有多层,也就是多维,1维就是向量,2维就是矩阵,3维就是$x y z$坐标轴构成的空间(形象理解),但体现在numpy中就是N 维数组对象 ndarray,它是一系列同类型数据的集合。

1维:

>>> import numpy as np
>>> a=np.array([1,2,3,4])
>>> print(a)
[1 2 3 4]
>>> type(a)
<class 'numpy.ndarray'>
>>> a.ndim
1

2维:

>>> import numpy as np
>>> b=np.array([[1,2,3],[4,5,6]])
>>> print(b)
[[1 2 3]
 [4 5 6]]
>>> type(b)
<class 'numpy.ndarray'>
>>> b.ndim
2
  • 切片和索引
    切片、索引与python内置的列表、字符串的切片和索引基本一样,如果理解了列表的切片和索引,那么ndarray对象就不在话下
>>> import numpy as np
>>> a=np.arange(10)
>>> print(a)
[0 1 2 3 4 5 6 7 8 9]
>>> type(a)
<class 'numpy.ndarray'>
>>> a.ndim
1
>>> a[:5]
array([0, 1, 2, 3, 4])
>>> a[7:]
array([7, 8, 9])
>>> a[3:6]
array([3, 4, 5])
>>> a[::2]
array([0, 2, 4, 6, 8])
>>> a[::-1]
array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])

>>> a[0]
0
>>> a[5]
5
  • 数组操作

修改数组形状

>>> import numpy as np
>>> a=np.arange(10)
>>> print(a)
[0 1 2 3 4 5 6 7 8 9]
>>> type(a)
<class 'numpy.ndarray'>
>>> a.ndim
1
>>> b=a.reshape(5,2)
>>> print(b)
[[0 1]
 [2 3]
 [4 5]
 [6 7]
 [8 9]]
>>> b.ndim
2
>>> c=a.reshape(2,5)
>>> print(c)
[[0 1 2 3 4]
 [5 6 7 8 9]]
>>> c.ndim
2

数组转置

>>> import numpy as np
>>> a=np.arange(12).reshape(3,4)
>>> print(a)
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
>>> np.transpose(a)
array([[ 0,  4,  8],
       [ 1,  5,  9],
       [ 2,  6, 10],
       [ 3,  7, 11]])
>>> a.T
array([[ 0,  4,  8],
       [ 1,  5,  9],
       [ 2,  6, 10],
       [ 3,  7, 11]])

数组连接
concatenatestackhstackvstack这个几个函数均是数组连接,原理基本都一样,只要理解了其中一个,其他的都很好理解,这里只介绍concatenate

>>> import numpy as np
>>> a=np.array([[1,2],[3,4]]
... )
>>> b=np.array([[5,6],[7,8]])
>>> np.con
np.concatenate( np.conj(        np.conjugate(   np.convolve(
>>> np.concatenate([a,b],axis=0)   #沿着0轴拼接
array([[1, 2],
       [3, 4],
       [5, 6],
       [7, 8]])
>>> np.concatenate([a,b],axis=1)   #沿着1轴拼接
array([[1, 2, 5, 6],
       [3, 4, 7, 8]])

修改数组维度

>>> import numpy as np
>>> x=np.array([1,2])
>>> np.expand_dims(x,axis=0)
array([[1, 2]])
>>> np.expand_dims(x,axis=1)
array([[1],
       [2]])
>>> y=np.array([[1,2]])
>>> np.squeeze(y)    #从给定数组的形状中删除一维,当前维必须等于1
array([1, 2])
  • 数组计算
>>> import numpy as np
>>> a1=np.array([1,2,3,4])
>>> a2=np.array([5,5,5,5])
>>> a1+a2
array([6, 7, 8, 9])
>>> np.add(a1,a2)
array([6, 7, 8, 9])
>>> a1-a2
array([-4, -3, -2, -1])
>>> np.subtract(a1,a2)
array([-4, -3, -2, -1])
>>> a1*a2
array([ 5, 10, 15, 20])
>>> np.multiply(a1,a2)
array([ 5, 10, 15, 20])
>>> a1/a2
array([0.2, 0.4, 0.6, 0.8])
>>> np.divide(a1,a2)
array([0.2, 0.4, 0.6, 0.8])

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号DataShare,不定期分享干货

背景

pathlib 标准库是在 Python3.4 引入,到现在最近版 3.11 已更新了好几个版本,主要是用于路径操作,相比之前的路径操作方法 os.path 有一些优势,有兴趣的同学可以学习下

官方文档: https://docs.python.org/zh-cn/3/library/pathlib.html 官方pathlib图片

小编环境

import sys

print('python 版本:',sys.version.split('|')[0])   #python 版本: 3.11.4

主要方法、函数

该模块中主要使用的是 Path

from pathlib import Path
Path.cwd()   #WindowsPath('D:/桌面/Python/标准库')
Path.home()   #WindowsPath('C:/Users/admin')
file = Path('pathlib_demo1.py')
print(file)   #WindowsPath('pathlib_demo1.py')
file.resolve()  #WindowsPath('D:/桌面/Python/标准库/pathlib_demo1.py')
file = Path('pathlib_demo1.py')
print(file)   #WindowsPath('pathlib_demo1.py')

file.stat()  
'''
os.stat_result(st_mode=33206, st_ino=1970324837176895, st_dev=2522074357, 
st_nlink=1, st_uid=0, st_gid=0, st_size=273, 
st_atime=1695642854, st_mtime=1695611301, st_ctime=1695611241)
'''

#文件大小
file.stat().st_size   #273B

#最近访问时间 access ,It represents the time of most recent access 
file.stat().st_atime  #1695625134.9083948

#创建时间 create,It represents the time of most recent metadata change on Unix and creation time on Windows.
file.stat().st_ctime  #1695611241.5981772

#修改时间  modify,It represents the time of most recent content modification
file.stat().st_mtime  #1695611301.1193473
for f in path.iterdir():
    print(f)
    print('is_file:',f.is_file())  #判断是否为文件
    print('is_dir:',f.is_dir())   #判断是否为文件夹
    print('='*30)

'''
D:\桌面\Python\标准库\.ipynb_checkpoints
is_file: False
is_dir: True
==============================
D:\桌面\Python\标准库\pathlib.ipynb
is_file: True
is_dir: False
==============================
D:\桌面\Python\标准库\pathlib_demo1.py
is_file: True
is_dir: False
==============================
'''
file=Path('D:\桌面\Python\标准库\pathlib_demo1.py')

file.name  #'pathlib_demo1.py'
file.stem  #'pathlib_demo1'
file.suffix   #'.py'
file.parent   #WindowsPath('D:/桌面/Python/标准库')
file.anchor  #'D:\\'
file.parent.parent  #WindowsPath('D:/桌面/Python')

#获取所有的父级路径,层层递进
list(file.parents)
'''
[WindowsPath('D:/桌面/Python/标准库'),
 WindowsPath('D:/桌面/Python'),
 WindowsPath('D:/桌面'),
 WindowsPath('D:/')]
'''

支持2种方式

#第1种方式:使用 /
Path.home() / 'dir' / 'file.txt'  #WindowsPath('C:/Users/admin/dir/file.txt')

#第2种方式:使用方法
Path.home().joinpath('dir', 'file.txt')  #WindowsPath('C:/Users/admin/dir/file.txt')
#当前文件件里面是否存在 子目录 archive/demo.txt 文件
Path("archive/demo.txt").exists()  #False

#当前文件件里面是否存在 二级子目录 dir/subdir  
Path('dir/subdir').exists()   #True

#当前文件件里面是否存在 pathlib_demo1.py 文件
Path("pathlib_demo1.py").exists()  #True

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

注:本文章由ChatGPT gpt-3.5-turbo 生成,小编进行略微调整 提出的问题:heapq详细讲解

背景

最近小编在读《Python Cookbook》书籍时,遇到一个新的标准库heapq,该库主要涉及堆数据结构,自己之前没有用过,所以就问了一下 ChatGPT,给出的内容非常详细且容易理解,分享出来供大家参考

heapq介绍

heapq 是 Python 标准库中的一个基于堆的优先队列实现。它提供了一些函数来实现对列表中的元素进行加入、弹出、替换等操作,同时也支持对列表中的元素进行建堆、堆排序等高级功能。本文将详细介绍 heapq 的使用方法和内部实现原理。

基本用法

1、heapq.heappush 和 heapq.heappop

heapq.heappush(lst, item):将元素 item 加入到列表 lst 中,并保持 lst 始终为一个堆。

heapq.heappop(lst):从列表 lst 中弹出最小的元素,并保持 lst 始终为一个堆。

下面是一个示例:

import heapq

lst = [10, 30, 2, 20, 50]  # 初始列表
heapq.heapify(lst)        # 建堆
print(lst)                # 输出 [2, 20, 10, 30, 50]

heapq.heappush(lst, 5)    # 将 5 加入到 lst 中
print(lst)                # 输出 [2, 5, 10, 30, 50, 20]

min_item = heapq.heappop(lst)  # 弹出最小元素
print(min_item, lst)           # 输出 2 [5, 20, 10, 30, 50]

2、heapq.heapreplace

heapq.heapreplace(lst, item):将最小元素替换为 item,并保持 lst 始终为一个堆。

这个函数相当于先执行 heapq.heappop(lst),再执行 heapq.heappush(lst, item)。使用该函数可以避免在先弹出再加入元素时,列表中出现不合法的状态。例如:

import heapq

lst = [10, 30, 2, 20, 50]   # 初始列表
heapq.heapify(lst)          # 建堆
print(lst)                  # 输出 [2, 20, 10, 30, 50]

heapq.heapreplace(lst, 5)   # 用 5 替换最小元素 2
print(lst)                  # 输出 [5, 20, 10, 30, 50]

3、heapq.nlargest 和 heapq.nsmallest

heapq.nlargest(n, lst):返回列表 lst 中前 n 个最大的元素,按照从大到小的顺序排序。

heapq.nsmallest(n, lst):返回列表 lst 中前 n 个最小的元素,按照从小到大的顺序排序。

这两个函数的实现都是先使用 heapq.heappush 和 heapq.heappop 对列表进行处理,之后返回前 n 个元素。示例:

import heapq

lst = [10, 30, 2, 20, 50]  # 初始列表
heapq.heapify(lst)        # 建堆
print(lst)                # 输出 [2, 20, 10, 30, 50]

print(heapq.nlargest(3, lst))  # 输出 [50, 30, 20]
print(heapq.nsmallest(3, lst)) # 输出 [2, 10, 20]

内部实现原理

Heap 是一种树形数据结构,通常用二叉树来实现。堆树的最上面是根节点,根节点下面的每个节点都比它自己所有的子节点都大(称为大根堆)或者都小(称为小根堆)。根据这个性质,堆树可以快速地找到最大或者最小元素。

Python 中的 heapq 模块实现是使用了一种叫做“二叉堆”的数据结构。二叉堆由固定数量的元素组成,堆的根节点包含所有能够在其中的元素中具有最小或者最大关键字的元素。我们称这个根节点为“最小堆”或者“最大堆”。堆中的每一个其他的节点都符合堆的性质:最小堆中的每一个节点都比它的子节点小;最大堆中的每一个节点都比它的子节点大。

这种数据结构可以直接用一个数组来实现,每个元素在数组中顺序存储,并按照堆的性质排列。数组的第一个元素是根节点,也就是堆的最小或最大元素。根据元素在数组中的位置,可以快速地用简单的数学运算找到它的子节点和父节点

二叉堆分为两种类型:最小堆和最大堆。在 Python 中的 heapq 模块中使用最小堆

Python 中,可以以列表的形式存储二叉堆,将列表作为二叉树,树的根节点即为第一个元素,树的子节点为列表中其左右孩子。具体来说,以第 k 个节点为例,其左孩子为第 2k+1 个节点,右孩子为第 2k+2 个节点,其父节点为第(k-1)//2 个节点

通过使用 heapq 模块提供的高效的堆算法,可以快速地实现对列表中元素的排序、寻找最大/最小值等常见操作

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

如果需要访问远程服务器的Mysql数据库,但是该Mysql数据库为了安全期间,安全措施设置为只允许本地连接(也就是你需要登录到该台服务器才能使用),其他远程连接是不可以直接访问,并且相应的端口也做了修改,那么就需要基于ssh来连接该数据库。这种方式连接数据库与Navicat里面界面化基于ssh连接一样。

Navicat

连接数据库

安装支持库

  • 如果要连接Mysql,首先需要安装pymysql
pip install pymysql
  • 安装基于ssh的库sshtunnel
pip install sshtunnel    #当前最新 0.3.1版

建议安装最新的sshtunnel库,旧版本库有一些bug

连接Mysql

基于ssh连接Mysql可以查看sshtunnel的文档,里面有一些案例

with SSHTunnelForwarder(
        ('192.168.1.1', 2222),
        ssh_password='123456',
        ssh_username='root',
        remote_bind_address=('127.0.0.1', 3306)) as server:
    print('SSH连接成功')
    conn = pymysql.connect(host='127.0.0.1',
                           port=server.local_bind_port,
                           user='root',
                           database='data',
                           charset='utf8')
    print('mysql数据库连接成功')
    cursor = conn.cursor()
    ...  #获取数据操作,此处省略
    cursor.close()
    conn.close()

自定义查询函数

可以对上面的连接进行封装为一个函数,方便其他地方使用

def mysql_ssh(sql,args=None):
    with SSHTunnelForwarder(
            ('192.168.1.1', 2222),
            ssh_password='123456',
            ssh_username='root',
            remote_bind_address=('127.0.0.1', 3306)) as server:
        print('SSH连接成功')
        conn = pymysql.connect(host='127.0.0.1',
                               port=server.local_bind_port,
                               user='root',
                               database='data',
                               charset='utf8')
        print('mysql数据库连接成功')
        cursor = conn.cursor()
        print('游标获取成功')
        try:
            print(f'执行查询语句:{sql}  参数:{args}')
            cursor.execute(sql,args)
            print('数据查询成功')
            conn.commit()
            print('事务提交成功')
            datas = cursor.fetchall()
            success = True
        except:
            print('数据查询失败')
            datas = None
            success = False

        print('正在关闭数据库连接')
        cursor.close()
        conn.close()

    return datas, success

注意点:

  • 在使用数据库时,conn.commit()cursor.close()conn.close() 这些一定要规范使用,防止不必要的bug
  • 传入参数时建议用这种方式cursor.execute(sql,args),防止sql注入的风险

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注 DataShare (同微),不定期分享干货

前言

小编第一次了解正则,是在VBA编程时用到,当时看了很多的学习资料,来了解和学习正则。因为现在数据录入、数据存放相对都比较规范,使用正则的场景越来越少,但运用正则在杂乱的数据中提取一些有用数据还是很方便,最近阅读书籍时又看到了正则相关的内容,于是总结了一下,分享出来,供大家参考学习

官方文档:https://docs.python.org/zh-cn/3/library/re.html

Excelhome精选正则文章
正则文章:正则表达式入门与提高---VBA平台的正则学习参考资料
地址:https://club.excelhome.net/thread-1128647-1-3.html

环境与正则库版本

import sys
import re 

print('python 版本:',sys.version.split('|')[0])   #python 版本: 3.11.4
print('re 正则库版本:',re.__version__)  #re 正则库版本: 2.2.1

正则模块中的函数/方法

  • re.compile 将正则表达式模式编译为一个正则表达式对象,方便多次使用
import re

text='Does this text match the pattern?'
regexes=re.compile('this')
print(regexes)   #re.compile('this')
print(regexes.search(text))  #<re.Match object; span=(5, 9), match='this'>
  • re.search 在给定的字符串中 查找/匹配 正则表达式模式,首次出现的位置,如果能匹配到,则返回相应的正则表达式对象;如果匹配不到,则返回 None
import re

pattern='this'
text='Does this text match the text pattern?'

match=re.search(pattern,text)

print(match)  #<re.Match object; span=(5, 9), match='this'>
print(match.re)  #re.compile('this')
print(match.re.pattern)  #this
print(match.string)  #Does this text match the text pattern?
print(match.start())  #5
print(match.end())  #9
  • re.match 在给定的字符串开头进行匹配,如果在开头能与给定的正则表达式模式匹配,则返回相应的正则表达式对象;如果匹配不到,则返回 None
import re

text='Does this text match the text pattern?'

match1=re.match('Does',text)
print(match1)   #<re.Match object; span=(0, 4), match='Does'>
print(match1.span())  #(0, 4)

match2=re.match('this',text)
print(match2)  #None
  • re.fullmatch 如果整个字符串需要与给定的正则表达式模式匹配,则返回相应的相应的正则表达式对象;如果匹配不到,则返回 None
import re

text='Does this text match the text pattern?'

match1=re.fullmatch('Does this text match the text pattern\?',text)
print(match1)   #<re.Match object; span=(0, 38), match='Does this text match the text pattern?'>

match2=re.fullmatch('Does this text',text)
print(match2)  #None


match3=re.fullmatch('Does .* pattern\?',text)
print(match3)  #<re.Match object; span=(0, 38), match='Does this text match the text pattern?'>
  • re.findall 对字符串与给定的正则表达式模式,从左至右进行查找,匹配结果按照找到的顺序进行返回,返回结果是以字符串列表或字符串元组列表的形式,如果匹配不到,返回空列表的形式
import re

text='Does this text match the text pattern?'

matches1=re.findall('text',text)
print(matches1)   #['text', 'text']

matches2=re.findall('regexes',text)
print(matches2)   #[]
  • re.finditerfindall 方法类似,结果返回的是一个迭代器,并且每个元素是匹配到的正则表达式对象
import re

text='Does this text match the text pattern?'

matches1=re.finditer('text',text)
print(matches1)  #<callable_iterator object at 0x0000024D0E9018D0>
for match in matches1:
    print(match) 
    #<re.Match object; span=(10, 14), match='text'>   
    #<re.Match object; span=(25, 29), match='text'>

matches2=re.findall('regexes',text)
print(matches2)  #[]

本篇文章只介绍了几个常用的方法,重点是方法的含义,而没有介绍元字符相关的内容,如果对正则表达式感兴趣,可以深入学习拓展知识范围

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

介绍

bisect模块提供了一种只针对 已排序的序列 的方法,快速找到插入元素的位置,这个模块使用二分查找算法,算法的时间复杂度相对更低一些,可以用于程序优化提升性能

官方文档:https://docs.python.org/3/library/bisect.html#module-bisect

函数分为 bisectinsort 两大块

各函数详解

  • bisect、bisect_right 这两个函数功能一模一样,bisect 是对 bisect_right 的引用,用于查找元素在已经排序的序列中应该插入的位置,返回值为最靠右 or 最大的索引位置
l = [1, 23, 45, 12, 23, 42, 54, 123, 14, 52, 3]
l.sort()

print(l)  #[1, 3, 12, 14, 23, 23, 42, 45, 52, 54, 123]
print(bisect.bisect(l, 3))  #2
  • bisect_left 返回值为最靠左 or 最小的索引
l = [1, 23, 45, 12, 23, 42, 54, 123, 14, 52, 3]
l.sort()

print(l)  #[1, 3, 12, 14, 23, 23, 42, 45, 52, 54, 123]
print(bisect.bisect_left(l, 3))  #1
  • insort、insort_right 这两个函数功能一模一样,insort 是对 insort_right 的引用,用于将一个元素插入到已经排序的序列中,并且保持序列的排序状态,插入位置为最靠右 or 最大的索引位置
l = [1, 23, 45, 12, 23, 42, 54, 123, 14, 52, 3]
l.sort()

print(l)  #[1, 3, 12, 14, 23, 23, 42, 45, 52, 54, 123]

bisect.insort(l, 3.0)
print(l)  #[1, 3, 3.0, 12, 14, 23, 23, 42, 45, 52, 54, 123]
  • insort_left 插入位置为最靠左 or 最小的索引位置
li = [1, 23, 45, 12, 23, 42, 54, 123, 14, 52, 3]
li.sort()

print(li)  #[1, 3, 12, 14, 23, 23, 42, 45, 52, 54, 123]

bisect.insort_left(li, 3.0)
print(li)  #[1, 3.0, 3, 12, 14, 23, 23, 42, 45, 52, 54, 123]

官方文档案例

def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
    i = bisect.bisect(breakpoints, score)
    return grades[i]

[grade(score) for score in [33, 99, 77, 70, 89, 90, 100]]
#['F', 'A', 'C', 'C', 'B', 'A', 'A']

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

最近在看《Python - 100天从新手到大师》时,运行了一下代码,感觉挺有意思。一个简单的小游戏,包含了pyhon的很多知识,分享出来,供大家参考学习

小编对扑克牌的排序进行了简单修改,使相同大小的牌放在一起

《Python - 100天从新手到大师》,感兴趣的同学可以去学习该教程
地址:https://github.com/jackfrued/Python-100-Days

发牌结果

发牌结果

小编环境

import sys

print('python 版本:',sys.version.split('|')[0])   
#python 版本: 3.11.4

完整代码

"""
===========================
@Software: PyCharm
@Platform: Win10
@Author : DataShare
===========================
"""

from enum import Enum, unique
import random


@unique
class Suite(Enum):
    """花色"""

    SPADE, HEART, CLUB, DIAMOND = range(4)

    def __lt__(self, other):
        return self.value < other.value


class Card:
    """牌"""

    def __init__(self, suite, face):
        """初始化方法"""
        self.suite = suite
        self.face = face

    def show(self):
        """显示牌面"""
        suites = ['♠︎', '♥︎', '♣︎', '♦︎']
        faces = ['', 'A', '2', '3', '4', '5', '6',
                 '7', '8', '9', '10', 'J', 'Q', 'K']
        return f'{suites[self.suite.value]}{faces[self.face]}'

    def __repr__(self):
        return self.show()


class Poker:
    """扑克"""

    def __init__(self):
        self.index = 0
        self.cards = [Card(suite, face)
                      for suite in Suite
                      for face in range(1, 14)]

    def shuffle(self):
        """洗牌(随机乱序)"""
        random.shuffle(self.cards)
        self.index = 0

    def deal(self):
        """发牌"""
        card = self.cards[self.index]
        self.index += 1
        return card

    @property
    def has_more(self):
        return self.index < len(self.cards)


class Player:
    """玩家"""

    def __init__(self, name):
        self.name = name
        self.cards = []

    def get_one(self, card):
        """摸一张牌"""
        self.cards.append(card)

    def sort(self, comp=lambda card: (card.face, card.suite)):
        """整理手上的牌"""
        self.cards.sort(key=comp)


def main():
    """主函数"""
    poker = Poker()
    poker.shuffle()
    players = [Player('东邪'), Player('西毒'), 
               Player('南帝'), Player('北丐')]
    while poker.has_more:
        for player in players:
                player.get_one(poker.deal())
    for player in players:
        player.sort()
        print(player.name, end=': ')
        print(player.cards)

if __name__ == '__main__':
    main()

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

简介

gihub地址: https://github.com/DQinYuan/chinese_province_city_area_mapper

cpca---chinese province city area,一个用于提取简体中文字符串中省,市和区并能够进行映射,检验和简单绘图的python模块

["徐汇区虹漕路461号58号楼5楼", "泉州市洛江区万安塘西工业区"]

地址
上海市上海市徐汇区虹漕路461号58号楼5楼
福建省泉州市洛江区万安塘西工业区

“地址”列:代表去除了省市区之后的具体地址

安装介绍

该库目前仅支持Python3,在命令行直接进行安装即可: pip install cpca

windows 中需要C/C++编译环境的支持,需要下载另外的软件,然后再进行安装 http://go.microsoft.com/fwlink/?LinkId=691126

编译环境的安装教程: https://o7planning.org/11467/install-microsoft-visual-cpp-build-tools

基本使用方法

常规用法

会自动补全相应的省、市、区

常规用法

import cpca

location_str = ["徐汇区虹漕路461号58号楼5楼", 
                "泉州市洛江区万安塘西工业区", 
                "北京朝阳区北苑华贸城"]

data=cpca.transform(location_str)

data

l=['灵宝市函谷关镇']

df = cpca.transform(l)
df

df.loc[0,"市"]

重名情况

中国的区级行政单位非常的多,经常有重名的情况,比如“北京市朝阳区”和“吉林省长春市朝阳区”,当有上级地址信息的时候,cpca 能够根据上级地址 推断出这是哪个区,但是如果没有上级地址信息,单纯只有一个区名的时候, cpca 就没法推断了,只能随便选一个了, 通过 umap 参数你可以指定这种情况下该选择哪一个

重名情况

从例子可以看出,umap 字典的 key 是区名,value 是区的 adcode,这里 110105 就是北京市朝阳区的 adcode,具体的 adcode 可以去全国行政区划查询平台上查询

全国行政区划查询平台: http://xzqh.mca.gov.cn/map

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

随着Python版本的更新,背后的一些数据结构会进行不断优化迭代,重新进行架构设计,以实现内存减少、性能提升。其中字典的底层数据结构在Python3.6版本时,重新进行了设计,从而优化了字典的内存占用

具体的底层细节这里不做过多介绍,感兴趣的同学可以看一下这篇文章: 《为什么Python 3.6以后字典有序并且效率更高?》 地址:https://zhuanlan.zhihu.com/p/73426505

该文章的评论精彩评论: 一句话解释:从Python3.6开始,dict的实现由 哈希表 改成 链式哈希表

小编环境

import sys

print('python 版本:',sys.version.split('|')[0])   
#python 版本: 3.11.4

测试代码

#创建测试数据
keys=[chr(i) for i in range(97,123)]
values=range(1,27)

#生成字典
dic={}
for key,value in zip(keys,values):
    dic[key]=value

#打印字典
print(dic)
#{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 
#'g': 7, 'h': 8, 'i': 9, 'j': 10, 'k': 11, 'l': 12,
# 'm': 13, 'n': 14, 'o': 15, 'p': 16, 'q': 17, 
#'r': 18,'s': 19, 't': 20, 'u': 21, 'v': 22,
# 'w': 23, 'x': 24, 'y': 25, 'z': 26}


#遍历字典
for key,value in dic.items():
    print(key,':',value,end=',')
#a : 1,b : 2,d : 4,e : 5,f : 6,g : 7,h : 8,j : 10,
#k : 11,l : 12,n : 14,o : 15,p : 16,q : 17,r : 18,
#s : 19,t : 20,u : 21,v : 22,w : 23,x : 24,z : 26,

#删除测试
del dic['c']
del dic['y']
del dic['i']
del dic['m']

print(dic)
#{'a': 1, 'b': 2, 'd': 4, 'e': 5, 'f': 6, 'g': 7, 
#'h': 8, 'j': 10, 'k': 11, 'l': 12, 'n': 14, 'o': 15,
#'p': 16, 'q': 17, 'r': 18, 's': 19, 't': 20,
#'u': 21, 'v': 22, 'w': 23, 'x': 24, 'z': 26}

结论

经过添加、删除操作可以看出,字典是按添加键值对时的先后顺序保存数据,是有序的

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

概述

math模块是内置模块,提供了许多对浮点数的数学运算函数,提供类似C语言标准定义的数学函数(This module provides access to the mathematical functions defined by the C standard)

包含以下 七部分 函数:

  • 算术函数(Number-theoretic and representation functions)
  • 幂函数与对数函数(Power and logarithmic functions)
  • 三角函数(Trigonometric functions)
  • 角度转换函数(Angular conversion)
  • 双曲函数(Hyperbolic functions)
  • 特殊函数(Special functions)
  • 常量(Constants)

math模块常用函数

虽然math模块提供的函数很多,但是现阶段工作中使用的很少,下面就列出一些实际工作中常用的函数:

注意:虽然math是内置模块,但使用前需要先import导入该库

import math

  • math.ceil(x)----------向上取整
>>> math.ceil(2.1)
3
>>> math.ceil(3.7)
4
>>> math.ceil(-1.5)
-1
>>> math.ceil(-3.1)
-3

  • math.floor(x)----------向下取整
>>> math.floor(1.2)
1
>>> math.floor(4.8)
4
>>> math.floor(-0.1)
-1
>>> math.floor(-2.8)
-3

  • math.exp(x)----------e的x次方,其中 e = 2.718281… 是自然对数的基数
>>> math.exp(1)
2.718281828459045
>>> math.exp(2)
7.38905609893065
>>> math.exp(0)
1.0

  • math.log(x,base=e)---------- 默认返回x 的自然对数,默认底为 e,如果指定底,返回指定底的对数
>>> math.log(math.exp(1))
1.0
>>> math.log(math.exp(0))
0.0
>>> math.log(math.exp(2))
2.0
>>> math.log(4,base=2)
2.0
>>> math.log(9,base=3)
2.0
>>> math.log(100,base=10)
2.0

  • math.pow(x, y)---------- x 的 y 次幂
>>> math.pow(2,3)
8.0
>>> math.pow(4,2)
16.0
>>> math.pow(-5,2)
25.0

  • math.sqrt(x)---------- x 的算术平方根,也就是正数的平方根
>>> math.sqrt(25)
5.0
>>> math.sqrt(4)
2.0
>>> math.sqrt(10)
3.1622776601683795

  • math.pi---------- 常量π,15位小数
>>> math.pi
3.141592653589793

  • math.e---------- 常量e,15位小数
>>> math.e
2.718281828459045

  • math.sin(x)---------- x弧度的正弦值
>>> math.sin(math.pi/2)
1.0
>>> math.sin(math.pi/3)
0.8660254037844386
>>> math.sin(math.pi/6)    #近似0.5
0.49999999999999994
>>> math.sin(math.pi/4)
0.7071067811865476

  • math.cos(x)---------- x弧度的余弦值
>>> math.cos(0)
1.0
>>> math.cos(math.pi/3)    #近似0.5
0.5000000000000001
>>> math.cos(math.pi/4)  
0.7071067811865476

  • math.degrees(x)----------将角度 x 从弧度转换为度数
>>> math.degrees(math.pi)
180.0
>>> math.degrees(math.pi/2)
90.0
>>> math.degrees(math.pi/6)    #近似30
29.999999999999996

  • math.radians(x)----------将角度 x 从度数转换为弧度
>>> math.radians(90)
1.5707963267948966
>>> math.radians(180)
3.141592653589793
>>> math.radians(360)
6.283185307179586

度数、弧度概念可参考历史相关文章,有详细说明

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号,不定期分享干货

介绍

偏函数(functools.partial),主要用来解决函数中某些参数是已知的固定值。利用偏函数的概念,可以生成一些新的函数,在调用这些新函数时,不用再传递固定值的参数,这样可以使代码更简洁

下面列举一些偏函数的巧妙使用方法,在使用偏函数时,需要从标准库functools中导入

from functools import partial

小编环境

import sys

print('python 版本:',sys.version.split('|')[0])   
#python 版本: 3.11.4

生成不同的聚合函数

1. 创建底层的元函数、函数类

from functools import partial

def aggregation_fn_meta(aggregation_fn, values):
    return aggregation_fn(values)

def aggregation_fn_class(aggregation_fn):
    return partial(aggregation_fn_meta, aggregation_fn)

2. 基于函数类,来生成不同的聚合函数

  • 基于内建函数创建(python中可以直接使用的函数)
sum_fn=aggregation_fn_class(sum)
sum_fn([1,2,3,4,5,1,2,10])   #28

max_fn=aggregation_fn_class(max)
max_fn([1,2,3,4,5,1,2,10])   #10

min_fn=aggregation_fn_class(min)
min_fn([1,2,3,4,5,1,2,10])
  • 基于自定义函数创建
def count(values):
    return len(values)

count_fn=aggregation_fn_class(count)
count_fn([1,2,3,4,5,1,2,10])    #8


def distinct_count(values):
    return len(set(values))

distinct_count_fn=aggregation_fn_class(distinct_count)
distinct_count_fn([1,2,3,4,5,1,2,10])   #6

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

前言

无论是在自己Windows、MacOS电脑,还是在Linux服务器,在操作文件时,多多少少都会涉及到文件的管理。 Python里面有个自带的 os 模块,专门是用来对文件、路径等进行管理的工具,下面列出一些自己在工作中常用的函数、方法,供大家参考学习。

路径的正确表示,三种都可以

  1. 由于\是转义的意思,所以路径都用\\表示,例如: 'C:\\Users\\abc\\Desktop'
  2. 如果想用单个\,可以在前面加个r,例如: r'C:\Users\abc\Desktop'
  3. 也可以用/来表示,例如: 'C:/Users/abc/Desktop'

点点们的介绍

./ 当前你所编辑的这个脚本所在目录 ../ 当前你所编辑的这个脚本所在目录的上一级目录

os 模块常用函数、方法

  • 无需pip安装,可以直接导入
import os

  • 获取当前工作路径 也就是你编写的这个脚本所在的文件夹位置
os.getcwd()    #C:\\Users\\abc\\Desktop\\Python\\python库

  • 获取绝对路径
path='./111.xlsx'
os.path.abspath(path)    #C:\\Users\\abc\\Desktop\\Python\\python库\\111.xlsx

  • 获取文件的完整路径里面的文件名字
a='C:\\Users\\abc\\Desktop\\Python\\python库\\111.xlsx'
os.path.basename(a)     #111.xlsx

  • 获取文件的完整路径里面的路径
a='C:\\Users\\abc\\Desktop\\Python\\python库\\111.xlsx'
os.path.dirname(a)     #C:\\Users\\abc\\Desktop\\Python\\python库

  • 判断是否存在相应的文件或文件夹
a='./111.xlsx'
b='C:\\Users\\abc\\Desktop\\Python\\python库\\111'
os.path.exists(a)    #True
os.path.exists(b)   #False

  • 分隔文件的完整路径为:路径、文件名字 相对上面的方法,这样可以一次都获取到,但是也有缺点,os.path.split只识别/,不识别\\,因此在用该方法时,需要先进行替换
a='C:\\Users\\abc\\Desktop\\Python\\python库\\111.xlsx'
b,c=os.path.split(a.replace('\\','/'))
#b  C:/Users/abc/Desktop/Python/python库
#c  111.xlsx

  • 删除存在的文件
a='./111.xlsx'
os.remove(a)    #无返回值,直接删除该文件

  • 创建文件夹 建议用makedirs方法,这样即可以直接创建单级文件夹,有可以创建多层级文件夹
os.makedirs('./a')
os.makedirs('./1/2')

以上这些方法是在工作中经常使用的,如有新的路径的需求可以在其他一些网站进行查找

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

有时写的Python程序需要交给业务人员使用,但业务人员电脑上基本都没有安装Python,并且业务人员也不会使用命令行,所以就需要把Python程序打包成exe可执行程序,让业务人员无需安装Python,可以直接使用。

这里只针对Windows操作系统的打包,以及只针对业务人员使用场景。(Linux系统基本都是技术开发人员在使用,基本都用的是命令行;而Mac系统不知是否有相关的打包库,可以打包为dmg)

打包过程(以下均为在cmd命令行执行)

  • 首先安装第三方库:pyinstaller
pip install pyinstaller
  • 打包
    需要先切换到打包程序目录, cd c:\xxx\xxx 然后对Python程序进行打包
pyinstaller -F xxx.py
  • 结果
    如果打包成功,当前目录下会增加一个新的dist文件夹,打开该文件夹,会发现打包好的exe文件:xxx.exe,文件名与Python程序文件相同
  • 其他
    打包大概流程如上所示,除此之外pyinstaller支持其他一些功能,比如打包时指定自定义图标,首先需要下载一张正常的ico,不能用直接修改后缀的,然后进行打包,一定是先图标文件路径,再是程序路径,如下所示:
pyinstaller -F  -i xxx.ico xxx.py

注意事项!!!

  • 运行报错
    虽然经过一番折腾,终于打包好exe可执行程序,但是双击运行时总是报错,无法成功运行,这种情况大多数是因为缺少第三方库造成的。

解决方法:
在打包之前先在cmd运行一次Python程序看是否成功运行

python xxx.py
  1. 如果能成功运行,那么打包后基本没什么问题
  2. 如果运行失败,那么查看报错信息,是否缺少第三方库,然后进行pip安装,确保能成功运行
  • 文件太大
    以上打包过程是不是很简单,但是有没有注意打包的exe文件有时会很大,有时几百兆大小,但是自己的Python程序也就几KB,这个问题也是自己之前遇到的难题(使用的是Anaconda),即使另外建立了新的环境也不行(conda create -n 环境名

解决方法:
一定要使用Python官网下载的原生Python程序,并且确保系统环境变量里面只有这一个Python路径,只有这一个Python路径,只有这一个Python路径!!!

亲测自己的打包程序从200M降到50M大小

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号DataShare,不定期分享干货

背景

懂编程语言最开始是属于程序猿的世界,现在随着国内人们受教育程度的提升、互联网科技的发展,业务人员也开始慢慢需要懂编程语言。从最近几年的招聘需求看,要求会Python则成为刚需。

业务人员之前使用的大部分都是Excel,现在随着数据量的提升,Excel已无法满足数据处理需求。如果在Excel里面数据量超过10万行,则Excel运行起来就相当卡顿。

下面展示一些在Excel里面常用的功能,看看其在Python里面具体是怎么实现的,Python处理数据用到的主要是pandas库,这也是《利用python进行数据分析》整本书介绍的对象。

如下所示为2021年2月编程语言排行榜: 从排行榜来看,python越来越吃香了 2021年2月编程语言排行榜

案例

这里只是展示方法,用到数据只有15行 案例数据

导入模拟数据

import pandas as pd
import numpy as np

data = pd.read_excel('模拟数据.xlsx')

data.head()

导入模拟数

查看数据行、列

len(data)    #数据行数

len(data.columns)    #数据列数

data.info()     #数据各列详细信息

data.describe()   #默认,值统计数值型列

data.describe(include='all')   #所有列

data.describe(include='object')   #只针对列为字符型

查看数据行、列

查看数据类型

data.dtypes

查看数据类型

数据筛选

data[data['性别']=='男']

data[data['年龄']>=30]

data[(data['年龄']>=30) & (data['性别']=='男')]   #两个条件 与

data[(data['年龄']>=30) | (data['性别']=='男')]    #两个条件 或

数据筛选

基于筛选,修改里面的数据

data.loc[data['姓名']=='张三','性别']='女'   #把张三 性别 修改为:女

data

修改数据

数据缺失值替换

data   #性别、年龄  里面各有个缺失值

int(data['年龄'].mean(skipna=True))

#年龄的缺失值,用平均值来代替
data['年龄'].fillna(int(data['年龄'].mean(skipna=True)),inplace=True)   

data

data['性别'].fillna('其他',inplace=True)

data

缺失值替换

添加行

#方法一
data.loc[15]=[16,'new',55,'女',350,4,50]
data

#方法二
data_new = pd.DataFrame([[16,'new',55,'女',350,4,50]],columns=data.columns)
pd.concat([data,data_new],ignore_index=True)

添加行

添加列

添加列相对比较简单,直接赋值即可

data['new_column_1']=0
data['new_column_2']='new'

添加列

删除行

data.loc[15]=[16,'new',55,'女',350,4,50,0,'new']    #先添加一个测试行

data

data.drop(index=15,inplace=True)    #删除行

data

删除行

删除列

data.drop(columns='new_column_1')    #返回删除后的新数据,原始数据不变

data.drop(columns=['new_column_1','new_column_2'])   #返回删除后的新数据,原始数据不变

data.drop(columns=['new_column_1','new_column_2'],inplace=True)   #在原始数据上处理

data

删除列

数据去重

data 

data[['性别','消费频次']]

data[['性别','消费频次']].drop_duplicates(keep='first')  #保留第1个,一般结合排序使用

data[['性别','消费频次']].drop_duplicates(keep='last') #保留最后1个,一般结合排序使用

#根据 性别、消费频次 2列进行去重
data.drop_duplicates(subset=['性别','消费频次'],keep='first')

数据去重

数据排序

相对Excel方便很多

data

data.sort_values(by='消费金额',ascending=True)

data.sort_values(by='消费金额',ascending=False)

data.sort_values(by=['消费频次','消费金额'],ascending=[False,True])

数据排序

数据统计

data

data['性别'].value_counts()

data['性别'].value_counts(normalize=True)  #百分比

data.value_counts(subset='性别')

data.value_counts(subset=['消费频次'],sort=True,ascending=True)

数据统计

数据透视表

data

pd.pivot_table(data,index=['性别'],aggfunc='count')

pd.pivot_table(data,index=['性别'],values=['姓名'],aggfunc='count')

pd.pivot_table(data,index=['性别'],
               columns=['消费频次'],
               values=['姓名'],
               aggfunc='count',
               fill_value=0)

pd.pivot_table(data,index=['性别'],
               columns=['消费频次'],
               values=['姓名'],
               aggfunc='sum',
               fill_value=0)

pd.pivot_table(data,index=['性别'],
               columns=['消费频次'],
               values=['消费金额'],
               aggfunc='sum',
               fill_value=0)

pd.pivot_table(data,index=['性别'],
               columns=['消费频次'],
               values=['最近一次消费距今间隔天数'],
               aggfunc='mean',
               fill_value=0)

数据透视表

sum函数

data

data['消费金额'].sum()

sum函数

count函数

data

data.count()

data['姓名'].count()

count函数

if函数

data

#方法一
data['性别_处理']=data['性别'].map(lambda x:1 if x=='男' else 0)

#方法二
def gender(x):
    if x=='男':
        return 1
    else:
        return 0

data['性别_处理2']=data['性别'].map(gender)

#方法三
dict_gender={'男':1,'女':0 ,'其他':0}
data['性别_处理3']=data['性别'].map(dict_gender)

if函数

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

在数据分析时,有时我们会碰到csv格式文件,需要先进行数据处理,转换成所需要的数据格式,然后才能进行分析

业务侧的同学可能对Excel文件比较熟悉,Excel可以把单个sheet直接保存为csv文件,也可以直接读取csv文件,变成Excel文件

技术侧的同学有时需要把数据库里面的数据导出到一个csv文件,有时也需要把别人给的csv文件加载到数据库中

csv文件在各个地方都这么流行,你真的彻底了解它吗?

CSV(逗号分隔值文件格式),逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号)

csv文件包含的各种数据

  • 常规的内容
    表格中: 常规内容-表格中
    csv文件中: 常规内容-csv文件中

  • 字段内部有逗号
    表格中: 字段内部有逗号-表格中
    csv文件中: 字段内部有逗号-csv文件中

  • 字段内部有引号
    表格中: 字段内部有引号-表格中
    csv文件中: 字段内部有引号-csv文件中

  • 字段内部有换行符
    表格中: 字段内部有换行符-表格中
    csv文件中: 字段内部有换行符-csv文件中

  • 字段内部有空格
    表格中: 字段内部有空格-表格中
    csv文件中: 字段内部有空格-csv文件中

csv文件规则

从上面的各种内容可以看出,当字段中包含特殊的字符时,在csv文件中会用双引号进行特殊处理

官方标准:
RFC4180:https://www.rfc-editor.org/rfc/rfc4180.txt
维基百科wiki:https://wiki.lazarus.freepascal.org/CSV

  • 字段内包含逗号, 双引号, 或是换行符的字段必须放在双引号内
  • 字段内包含引号必须在其前面增加一个引号,来实现引号的转码
  • 元素中的换行符将被保留下来
  • 分隔符逗号前后的空格仍然会被保留

用pandas进行解析

  • 常规的内容 常规的内容-解析
import pandas as pd

data1=pd.read_csv('1-常规的内容.csv',encoding='GB2312')

data1
  • 字段内部有逗号 字段内部有逗号-解析
import pandas as pd

data1=pd.read_csv('2-字段内部有逗号.csv',encoding='GB2312',quotechar='"')

data1
  • 字段内部有引号 字段内部有引号-解析
import pandas as pd

data1=pd.read_csv('3-字段内部有引号.csv',encoding='GB2312',quotechar='"')

data1
  • 字段内部有换行符 该程序是在 Windows 平台运行,换行符为 \r\n 字段内部有换行符-解析
import pandas as pd

data1=pd.read_csv('4-字段内部有换行符.csv',
                  encoding='GB2312',
                  quotechar='"',
                  engine='python')

data1
  • 字段内部有空格 字段内部有空格-解析
import pandas as pd

data1=pd.read_csv('5-字段内部有空格.csv',
                  encoding='GB2312',
                  quotechar='"')

data1

pd.read_csv部分参数解释

import pandas as pd
print(pd.__version__)   #1.3.4

完整的参数:

pd.read_csv(
    filepath_or_buffer: 'FilePathOrBuffer',
    sep=<no_default>,delimiter=None,header='infer',names=<no_default>,
    index_col=None,usecols=None,squeeze=False,prefix=<no_default>,
    mangle_dupe_cols=True,dtype: 'DtypeArg | None' = None,
    engine=None,converters=None,true_values=None,
    false_values=None,skipinitialspace=False,skiprows=None,
    skipfooter=0,nrows=None,na_values=None,keep_default_na=True,
    na_filter=True,verbose=False,skip_blank_lines=True,
    parse_dates=False,infer_datetime_format=False,keep_date_col=False,
    date_parser=None,dayfirst=False,cache_dates=True,iterator=False,
    chunksize=None,compression='infer',thousands=None,
    decimal: 'str' = '.',lineterminator=None,quotechar='"',
    quoting=0,doublequote=True,escapechar=None,
    comment=None,encoding=None,encoding_errors: 'str | None' = 'strict',
    dialect=None,error_bad_lines=None,warn_bad_lines=None,
    on_bad_lines=None,delim_whitespace=False,low_memory=True,
    memory_map=False,float_precision=None,storage_options: 'StorageOptions' = None,
)

下面主要解释一些常用的参数:
  • sep
    sep参数是字符型的,代表每行数据内容的分隔符号,默认是逗号,另外常见的还有制表符(\t)、空格等,根据数据的实际情况传值 还提供了一个参数名为delimiter的定界符,这是一个备选分隔符,是sep的别名,效果和sep一样。如果指定该参数,则sep参数失效

  • dtype
    指定各数据列的数据类型,建议在导入数据时全部使用字符型,dtype='str',后面在数据处理时再转换为需要的类型

  • engine
    解析器、引擎,可以选择C或Python。 C语言的速度最快,Python语言的功能最为完善

  • iterator
    是否设置为迭代器,如果设置为True,则返回一个TextFileReader对象,并可以对它进行迭代,以便逐块处理文件,一般结合chunksize使用,指定文件块的大小,分块处理大型CSV文件

  • lineterminator
    每行的解释符号,但只能允许一个字符长度,仅对C解析器有效

  • quotechar
    字段之间的定界符,这样就能正确解析包含特殊符号的字段了

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

最近在做数据处理时,发现别人给的 csv 文件用 txt 打开后,发现里面的所有字段都是带双引号,与自己之前见过的 csv 文件有点不一样,自己脑海里面隐约也见过 python 有相关的设置参数,于是就查看 python 官方文档中的 csv 模块介绍,总结分享出来予以记录,方便后续查看

csv文档地址:https://docs.python.org/zh-cn/3.11/library/csv.html
csv代码:https://github.com/python/cpython/blob/3.11/Lib/csv.py

csv 模块的常量是从 _csv 模型引入,_csv 是用 c 语言编写 _csv模块引入

_csv c语言代码片段

csv模块定义的常量说明

  • csv.QUOTE_ALL,等于0
    指示 writer 对象给所有字段加上引号
  • csv.QUOTE_MINIMAL,等于1
    指示 writer 对象仅为包含特殊字符(例如 定界符(delimiter)引号字符(quotechar)  或  行结束符(lineterminator) 中的任何字符)的字段加上引号
  • csv.QUOTE_NONNUMERIC,等于2
    指示 writer 对象为所有非数字字段加上引号 指示 reader 将所有未用引号引出的字段转换为 float 类型
  • csv.QUOTE_NONE,等于3
    指示 writer 对象不使用引号括住字段。当 定界符(delimiter) 出现在输出数据中时,其前面应该有 转义符(escapechar)。如果未设置 转义符(escapechar),则遇到任何需要转义的字符时,writer 都会抛出 Error 异常 指示 reader 不对引号字符进行特殊处理

常量

在 pandas 中的案例演示

模拟数据 模拟数据

pandas to_csv 默认使用的是 csv.QUOTE_MINIMAL
文档:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_csv.html to_csv quoting

csv.QUOTE_ALL 生成csv文件 QUOTE_ALL

csv.QUOTE_MINIMAL 生成csv文件 QUOTE_MINIMAL

csv.QUOTE_NONNUMERIC 生成csv文件 QUOTE_NONNUMERIC

csv.QUOTE_NONE 生成csv文件 QUOTE_NONE

jupyter-notebook 完整代码 生成csv

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

由于身边同事经常买双色球,时间长了也就慢慢关注这个,我们中午经常也一块去吃饭,然后去彩票站点。之前是在支付宝上面就可以买,那会自己也会偶尔买10元的。这片文章主要是爬取了历史双色球所有数据,并进行简单分析,纯属业余爱好,分析结果仅供参考。

1.数据爬取网页

历史双色球数据:https://datachart.500.com/ssq/

 #分析网页后可以得知get历史所有数据的参数
url='https://datachart.500.com/ssq/history/newinc/history.php?start=03001'  

#加载相关的库
import requests
import numpy as np
import pandas as pd

#获取历史所有双色球数据
response = requests.get(url)
response.encoding = 'utf-8'  
re_text = response.text

#网页数据解析
re=re_text.split('<tbody id="tdata">')[1].split('</tbody>')[0]
result=re.split('<tr class="t_tr1">')[1:]

all_numbers=[]
for i in result:
    each_numbers=[]
    i=i.replace('<!--<td>2</td>-->','')
    each=i.split('</td>')[:-1]
    for j in each:
        each_numbers.append(j.split('>')[1].replace('&nbsp;',''))
    
    all_numbers.append(each_numbers)
  
#定义列名称  
col=['期号','红球1','红球2','红球3','红球4','红球5','红球6','蓝球','快乐星期天','奖池奖金(元)',
     '一等奖注数','一等奖奖金(元)','二等奖注数','二等奖奖金(元)','总投注额(元)','开奖日期']

#解析完网页数据,生成双色球数据框
df_all=pd.DataFrame(all_numbers,columns=col)
df_all.head()

双色球

2.数据转换

#日期转换
df_all['开奖日期_dt']=pd.to_datetime(df_all['开奖日期'])
df_all['year']=df_all['开奖日期_dt'].dt.year
df_all['month']=df_all['开奖日期_dt'].dt.month
df_all['day']=df_all['开奖日期_dt'].dt.day
df_all['weekday']=df_all['开奖日期_dt'].dt.weekday_name
df_all.head()

双色球2.png

#one-hot 编码转换自定义函数
def lotterydata(df):
    modeldata=df.copy()
    
    redball=[]
    for i in range(1,34):
        redball.append('红球'+'%02d'%i)
    for i in redball:
        modeldata[i]=0
    
    blueball=[]
    for i in range(1,17):
        blueball.append('蓝球'+'%02d'%i)
    for i in blueball:
        modeldata[i]=0
        
    
    for row in range(modeldata.shape[0]):
        #print(row)
        #print(modeldata.iloc[row,:])
        for i in redball:
            #print(i)
            #modeldata[i]=0
            if (modeldata.iloc[row,:]['红球1']==i[-2:] or modeldata.iloc[row,:]['红球2']==i[-2:] 
                or modeldata.iloc[row,:]['红球3']==i[-2:] or modeldata.iloc[row,:]['红球4']==i[-2:] 
                or modeldata.iloc[row,:]['红球5']==i[-2:] or modeldata.iloc[row,:]['红球6']==i[-2:]):
                modeldata.loc[row,i]=1

        for j in blueball:
            #modeldata[j]=0
            if modeldata.iloc[row,:]['蓝球']==j[-2:]:
                modeldata.loc[row,j]=1
    return modeldata

#生成各颜色球的0-1编码
modeldata=lotterydata(df_all)
modeldata.head()

双色球3.png

3.数据分析与展示

allhistorydata=modeldata.iloc[:,-49:].copy()

#历史所有红球和蓝球数据
allhistorydata_red=allhistorydata.iloc[:,:33]
allhistorydata_blue=allhistorydata.iloc[:,-16:]

#最近20期红球和最近48期蓝球
#(33*3)/6  每个红球有3次出现机会,看一共需要多少期,这里取整数20期
#(16*3)/1  每个蓝球有3次出现机会,看一共需要多少期
recently20_red=allhistorydata.iloc[:20,:33]
recently48_blue=allhistorydata.iloc[:48,-16:]

#求和
historyred_sum=allhistorydata_red.sum()
historyblue_sum=allhistorydata_blue.sum()

recently20red_sum=recently20_red.sum()
recently48blue_sum=recently48_blue.sum()

#排序
historyred_sum=historyred_sum.sort_values(ascending=True)
historyblue_sum=historyblue_sum.sort_values(ascending=True)

recently20red_sum=recently20red_sum.sort_values(ascending=True)
recently48blue_sum=recently48blue_sum.sort_values(ascending=True)

#数据展示
import matplotlib.pyplot as plt

%matplotlib inline
plt.rcParams['font.sans-serif'] = ['SimHei']    #显示中文

plt.figure(figsize=(30,24),facecolor='snow')

#历史出现次数最少的10个红球
x_red=historyred_sum.index.map(lambda x:x[-2:])[:10]
y_red=historyred_sum.values[:10]

#历史出现次数最少的5个蓝球
x_blue=historyblue_sum.index.map(lambda x:x[-2:])[:5]
y_blue=historyblue_sum.values[:5]

plt.subplot(3,2,1)
plt.bar(x_red,y_red,width=0.4,align='center',color='r')
for a,b in zip(x_red,y_red):
    plt.text(a,b,b,ha='center',va='bottom',fontsize=15)
plt.tick_params(axis='x',labelsize=30)
plt.title("历史出现次数最少的10个红球",fontsize=30)

plt.subplot(3,2,2)
plt.bar(x_blue,y_blue,width=0.2,align='center',color='b')
for a,b in zip(x_blue,y_blue):
    plt.text(a,b,b,ha='center',va='bottom',fontsize=15)
plt.tick_params(axis='x',labelsize=30)
plt.title("历史出现次数最少的5个蓝球",fontsize=30)

#最近20期红球
x20_red=recently20red_sum.index.map(lambda x:x[-2:])
y20_red=recently20red_sum.values

#最近48期蓝球
x48_blue=recently48blue_sum.index.map(lambda x:x[-2:])
y48_blue=recently48blue_sum.values

plt.subplot(3,1,2)
plt.bar(x20_red,y20_red,width=0.5,align='center',color='r')
for a,b in zip(x20_red,y20_red):
    plt.text(a,b,b,ha='center',va='bottom',fontsize=15)
plt.tick_params(axis='x',labelsize=25)
plt.title("最近20期红球情况",fontsize=30)

plt.subplot(3,1,3)
plt.bar(x48_blue,y48_blue,width=0.5,align='center',color='b')
for a,b in zip(x20_blue,y20_blue):
    plt.text(a,b,b,ha='center',va='bottom',fontsize=15)
plt.tick_params(axis='x',labelsize=25)
plt.title("最近48期蓝球情况",fontsize=30)

plt.show()

最终的数据展示结果,仅供参考!!!

数据展示结果


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

介绍

众所周知地球是一个球体,地平面是一个弧形,那么两个地理位置之间的中点该如何确定,比如北京与上海两个城市之间的中点在哪里? 可以直接对经纬度进行平均,求中点吗? 答案:当然不可以

我们都知道一个地理位置是由经度、维度来确定,平时在计算地理信息时基本都是两列数据,一列是经度,一列是维度,比如:
北京:"lon":116.512885,"lat":39.847469,
上海:"lon":116.332334,"lat":39.882806

  • lon:longitude 经度
    经度是指通过某地的经线面与本初子午面所成的二面角
    在本初子午线以东的经度叫东经,在本初子午线以西的叫西经。
    东经用“E”表示,西经用“W”表示。

  • lat:latitude 维度
    赤道的纬度为0°,将行星平分为南半球和北半球。
    纬度是指某点与地球球心的连线和地球赤道面所成的线面角,其数值在0至90度之间。
    位于赤道以北的点的纬度叫北纬,记为N,位于赤道以南的点的纬度称南纬,记为S。

概念-----计算原理

那么应该怎么计算呢? 我们可以把地球看做是一个立体坐标系,有x轴,y轴,z轴三个方向,对于球面上的一个点,可以分别计算出在x轴,y轴,z轴的投影,那么在三个轴上面的分量是可以直接求均值,最后再进行反向合成,这样即可求出球面上对应的中点。

下图手工画出了,在x轴,y轴,z轴如何进行投影(可进行参考): 球面点进行分解 根据手绘图可以计算出,各个分量:
x = cos(lat) * sin(lon)
y = cos(lat) * cos(lon)
z = sin(lat)

角度和弧度相互转换
由于我们实际数据中的经纬度是角度,而我们在计算分量时需要用弧度,比如 cos(π/2) ,这里就先需要转换,那么怎么进行转换:
python的math模块里面有相应转换函数
radians()-----将角度转换为弧度
degrees()-----将弧度转换为角度

脑补一下角度与弧度:
度和弧度都是衡量角的大小的单位,就像米(m)和英寸(in)都是用来衡量长度的单位。度用°来表示,弧度用rad表示。
1rad = (180/π)° ≈ 57.3°
1° = (π/180)rad ≈ 0.01745rad

弧度的定义
在一个圆中,弧长等于半径的弧,其所对的圆心角就是 1rad。也就是说,两条射线从圆心向圆周射出,形成一个夹角和夹角正对的一段弧。当这段弧的长度正好等于圆的半径时,两条射线的夹角的弧度为 1。 弧度 根据定义,圆一周的弧度数为 2πr/r = 2π,360° = 2πrad,平角(即 180° 角)为 πrad,直角为 π/2rad 角度与弧度 在具体计算中,角度以弧度给出时,通常不写弧度单位,直接写值。最典型的例子是三角函数,例如:sin(8π)、tan(3π/2)

自定义函数

import pandas as pd
import numpy as np
from math import cos, sin, atan2, sqrt, radians, degrees

data=pd.read_excel('./data.xlsx')

def center_geolocation(df): 
    """
    输入多个经纬度坐标,找出中心点  [lon,lat]->[经度,维度]
    :param geolocations: 列表
    :return 中心经纬度
    """
    x = 0
    y = 0
    z = 0
    lenth = len(df)
    for lon, lat in zip(df['lon'].to_list(),df['lat'].to_list()):
        lon = radians(float(lon))    #radians将角度转换为弧度
        lat = radians(float(lat))
        x += cos(lat) * sin(lon)
        y += cos(lat) * cos(lon)
        z += sin(lat)
    
    x = float(x / lenth)
    y = float(y / lenth)
    z = float(z / lenth)
    
    #degrees将弧度转换为角度
    lon=degrees(atan2(x, y))
    lat=degrees(atan2(z, sqrt(x * x + y * y)))
    
    return pd.DataFrame({'lon':[lon],'lat':[lat]})    

#使用
result_data=data.groupby('ID').apply(center_geolocation)
result_data.index=result_data.index.droplevel(level=1)
result_data.to_excel('./result_data.xlsx')

以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

Excel里面数据透视表可谓是功能强大,可以对数据进行去重,可以方便的对数据进行汇总,可以对数据从不同维度进行交叉分析等,而且速度还非常快,即使有几万行数据。

当然在Python里面也有数据透视函数,但是没有Excel这么灵活,比如今天要介绍的这种情况,在值里面要一列放聚合的求和,一列放聚合后的占比,这在Excel里面可以非常方便的利用数据透视表的功能 值显示方式 来解决,今天这篇文章利用 自定义函数 来实现这个功能。

模拟数据

模拟数据

需要类似Excel数据透视表的结果

Excel数据透视表

自定义函数

利用pandas的 pivot_table 函数可以实现求和功能,然后再让该列值除以总和来实现占比的功能

def pivot_percent(df, index, value, decimal=2):
    '''
    该函数 index、value 仅支持一个字段
    df: 传入整个数据框
    index: 维度
    value: 汇总的值
    decimal: 百分比保留的小数位数
    '''
    
    data_pivot = pd.pivot_table(data, values=value, index=index, aggfunc='sum')
    data_pivot['占比'] = data_pivot[value]/data_pivot[value].sum()
    data_pivot = data_pivot.sort_values(by=value, ascending=False)
    
    #添加总计行
    data_new = pd.DataFrame([[data_pivot[value].sum(), 1]],
                            columns=data_pivot.columns,
                            index=['总计'])
    data_result = pd.concat([data_pivot, data_new], ignore_index=False)
    
    #格式化百分比
    data_result['占比'] = data_result['占比'].map(lambda x: f'{x:.{decimal}%}')

    return data_result

jupyter notebook 完成代码

jupyter notebook

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

习惯了Excel里面的透视表拖拽方式,在Python中的pandas怎么能方便使用透视函数呢,有时可能会感到困惑,但是Excel中的透视表功能,pandas基本都能实现,下面进行详细介绍:

生成透视表函数

pd.pivot_table(data,values=None,index=None,columns=None,aggfunc='mean',fill_value=None,margins=False,dropna=True,margins_name='All')
详细介绍每个参数:
data:为了生成透视表需要用到的数据框,对应Excel里面的需要用到的区域
values:对那个字段进行值计算,对应Excel里面需要把字段拖拽到值的地方
index:根据字段进行汇总,生成每行一个分类,对应Excel里面需要把字段拖拽到行的地方
columns:根据字段进行汇总,生成每列一个分类,对应Excel里面需要把字段拖拽到列的地方
aggfunc:对值字段进行那种计算,计数、求和、平均,对应Excel里面对值字段设置里面的值汇总方式选择
fill_value:行分类与列分类交叉值为空的地方用什么值填充
margins:是否对行列显示汇总,对应Excel里面透视表下面设计选型卡,总计是否对行和列启用
dropna:是否包括原始引用数据里面都为NAN的列
margins_name:可以给总计的列起别名
行、列、值对应 aggfunc对应

示例

  • 导入数据
import pandas as pd
data=pd.read_excel('111.xlsx',sheet_name='python')
data

data

  • 创建数据透视表
pd.pivot_table(data,values =['数值1','数值2'],index=['字段1','字段2','字段3'],aggfunc='sum')

数据透视表

  • 查看生成的数据透视表是什么
    可以看到生成的数据透视表还是dataframe数据框,那么数据框能用的一切方法同样适用于生成的这个透视表
data_result=pd.pivot_table(data,values =['数值1','数值2'],
                           index=['字段1','字段2','字段3'],
                           aggfunc='sum')
type(data_result)

数据透视表是什么类型

  • 添加行列总计
#添加margins参数
pd.pivot_table(data,values =['数值1','数值2'],
                    index=['字段1','字段2'],
                    columns=['字段3'],
                    aggfunc='sum',
                    margins=True)

添加行列总计

  • 转换成正常的数据框
    重置索引转换成正常的数据框样式
data_result=pd.pivot_table(data,values =['数值1','数值2'],
                           index=['字段1','字段2','字段3'],
                           aggfunc='sum').reset_index()

重置索引

  • 生成带列分类的透视表
pd.pivot_table(data,values =['数值1','数值2'],
                    index=['字段1','字段2'],
                    columns=['字段3'],
                    aggfunc='sum')

带列分类数据框

  • 不同层级之间的调换
    比如上面生成带列字段分类的透视表,需要把字段3(f、t)的一行和上面数值1、数值2的一行进行调换
#先进行一下赋值
data_result=pd.pivot_table(data,values =['数值1','数值2'],
                    index=['字段1','字段2'],
                    columns=['字段3'],
                    aggfunc='sum')
#修改列的名称
data_result.columns.names=['一','二']

#进行调换
data_result.swaplevel('二','一',axis=1)

#为了对相同内容放到一块,进行排序
data_result.swaplevel('二','一',axis=1).sort_index(level=0,axis=1)

不同层级之间调换

  • 根据一列进行排序
#赋值操作
data_result=data_result.swaplevel('二','一',axis=1).sort_index(level=0,axis=1)

#进行排序
data_result.sort_values(by=[('f','数值2')],axis=0)

数值排序


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

主要区别

  • shuffle没有返回值,直接在原来的数据上进行打乱排序,没有返回;而permutation是在数据副本上面进行打乱,返回打乱之后的副本。
  • 由于permutation会复制数据,所以当数据量特别大的时候,使用shuffle的效率更高。
  • 无论是shuffle还是permutation对二维及以上数据,都是只对第一维进行打乱顺序,第二维中的顺序并不会打乱。

示例

示例


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

在数据处理时,对原始数据进行筛选操作,在不注意情况下,会引发FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison 警告,究其根本原因就是在进行筛选时,对不同类型进行了比较,导致返回错误的结果

复现

可以看出,字段5是有2个7,现在想筛选出包含7的行 模拟数据

在进行筛选时,对列进行比较,引发错误提醒,FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison,导致没有筛选出结果 错误提醒

解决方法

由于在筛选时用的是逻辑索引,可以先看看逻辑索引结果 逻辑索引结果

可以看出,逻辑索引结果均为False,所以没有成功筛选出数据,由于模拟的数据量比较小,咱们基本一眼就能看出问题所在,那就是在进行比较时,字段5的类型明显为数值型,而在进行比较时用的是字符型 ‘7’,所以导致引发错误提醒,当在进行大量数据操作时,这种错误可能会很难发现

先进行类型转换,然后再进行比较,即可得出正确的结果

解决方法

总结

我们在进行大数据操作时,一定要对数据类型进行确认,并且是真实的数据类型,至于为什么是真实的数据类型,详情可参考历史文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

在Python里面处理数据,必然离不开Pandas,但目前网上的文章大部分都是介绍函数怎么使用,至于为什么有时数据处理结果是错误的,并没有深入研究,也可能是由于Pandas的一些BUG,没有人提出,下面介绍几个误区,看与你一直以为的一样吗

数据准备

平时大家都是从其他一些文件或数据库导入,在这里直接手动模拟一些数据,大家可以仔细看看这些数据有什么区别

>>> import pandas as pd
>>> df=pd.DataFrame({'x':[1,1,2,3,4,5,5,6],
                     'y':['A','B','C','D','E','F','E','A'],
                     'z':[11,21,32,34,'65',56,31,45]})
>>> df
   x  y   z
0  1  A  11
1  1  B  21
2  2  C  32
3  3  D  34
4  4  E  65
5  5  F  56
6  5  E  31
7  6  A  45

df['x'] 与 df[['x']] 区别

乍一看上去,这两个可能并没有什么区别,都是对x列的引用,其实是有区别的,并且区别还是比较大,不信就看看

>>> df['x']
0    1
1    1
2    2
3    3
4    4
5    5
6    5
7    6
Name: x, dtype: int64
>>> df[['x']]
   x
0  1
1  1
2  2
3  3
4  4
5  5
6  5
7  6
>>> 

df['x'] 与 df[['x']]区别 上面程序打印结果一下看不出什么区别,下面的图片是不是一下就有区别了,为什么一个没有显示格式,一个是有格式;一个显示变量名称、数据类型,一个什么都没有,那就接着往下看,打印一下他们是Pandas里面的什么类型

>>> type(df['x'])
<class 'pandas.core.series.Series'>
>>> type(df[['x']])
<class 'pandas.core.frame.DataFrame'>

是不是恍然大悟,虽然都是对x列变量的引用,但是返回的结果是不一样的,一个是Series,一个是DataFrame,那么后续在这个结果上的操作肯定也是有很大区别,Series与DataFrame的方法、属性 各有千秋,区别很大

数据类型'object'

在创建数据时,一共创建了3列,其中x列与z列看出有什么区别了吗?没有仔细看的认为两列不都是数值吗???,其实并不然

>>> df.dtypes
x     int64
y    object
z    object
dtype: object

怎么z列是object类型,那就再看一下创建数据时的z到底是什么: 'z':[11,21,32,34,'65',56,31,45],中间的'65'是字符型,所以导致zobject类型,也就说明: pandas在检查数据类型时,当遇到数值型与字符型时,用字符型类型来代表这一列的类型,但里面每个值具体的类型还是原来的真实类型;都是数值型的则会转变,具体测试如下所示

>>> df1=pd.DataFrame({'x':[1,1,2,3.0,4,5,5,6],'y':['A','B','C','D','E','F','E','A']})
>>> df1
     x  y
0  1.0  A
1  1.0  B
2  2.0  C
3  3.0  D
4  4.0  E
5  5.0  F
6  5.0  E
7  6.0  A
>>> df1.dtypes
x    float64
y     object
dtype: object
>>> df
   x  y   z
0  1  A  11
1  1  B  21
2  2  C  32
3  3  D  34
4  4  E  65
5  5  F  56
6  5  E  31
7  6  A  45
>>> for i in df['z']:
	  print(type(i))
<class 'int'>
<class 'int'>
<class 'int'>
<class 'int'>
<class 'str'>
<class 'int'>
<class 'int'>
<class 'int'>
>>> for i in df1['x']:
	  print(type(i))
<class 'float'>
<class 'float'>
<class 'float'>
<class 'float'>
<class 'float'>
<class 'float'>
<class 'float'>
<class 'float'>

以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

数学概念

范数,是具有 “长度” 概念的函数。在线性代数、泛函分析及相关的数学领域,范数是一个函数,是矢量空间内的所有矢量赋予非零的正长度或大小。

在数学上,范数包括向量范数矩阵范数

L1 范数和 L2 范数,用于机器学习的 L1 正则化、L2 正则化。对于线性回归模型,使用 L1 正则化的模型建叫做 Lasso 回归,使用 L2 正则化的模型叫做 Ridge 回归(岭回归)。

其作用是: L1 正则化是指权值向量 w 中各个元素的绝对值之和,可以产生稀疏权值矩阵(稀疏矩阵指的是很多元素为 0,只有少数元素是非零值的矩阵,即得到的线性回归模型的大部分系数都是 0. ),即产生一个稀疏模型,可以用于特征选择;

L2 正则化是指权值向量 w 中各个元素的平方和然后再求平方根,可以防止模型过拟合(overfitting);一定程度上,L1 也可以防止过拟合。

Numpy函数介绍

np.linalg.norm(x, ord=None, axis=None, keepdims=False)

np.linalg.norm:linalg=linear(线性)+algebra(代数),norm则表示范数

  • x:表示矩阵(也可以是一维)
  • ord:范数类型
  • axis:轴向 axis=1表示按行向量处理,求多个行向量的范数 axis=0表示按列向量处理,求多个列向量的范数 axis=None表示矩阵范数。
  • keepdims:是否保持矩阵的二维特性 True表示保持矩阵的二维特性,False相反

范数

例子

  • 向量
>>> import numpy as np
>>> x=np.array([1,2,3,4])
>>> np.linalg.norm(x)      #默认是二范数,所有向量元素绝对值的平方和再开方
5.477225575051661
>>> np.sqrt(1**2+2**2+3**2+4**2)
5.477225575051661
>>> np.linalg.norm(x,ord=1)    #所有向量元素绝对值之和
10.0
>>> 1+2+3+4
10
>>> np.linalg.norm(x,ord=np.inf)     #max(abs(x_i)),所有向量元素绝对值中的最大值
4.0
>>> np.linalg.norm(x,ord=-np.inf)   #min(abs(x_i)),所有向量元素绝对值中的最小值
1.0
  • 矩阵
>>> import numpy as np
>>> x=np.arange(12).reshape(3,4)
>>> x
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>> np.linalg.norm(x)  #默认是二范数,最大特征值的算术平方根
22.494443758403985
>>> np.linalg.norm(x,ord=1)   #所有矩阵列向量绝对值之和的最大值
21.0
>>> x
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>> np.linalg.norm(x,ord=1,axis=1)   #行向量的一范数
array([ 6., 22., 38.])
>>> np.linalg.norm(x,ord=2,axis=1)     #行向量的二范数
array([ 3.74165739, 11.22497216, 19.13112647])
>>> np.linalg.norm(x,ord=1,axis=1,keepdims=True)   #结果仍然是个矩阵
array([[ 6.],
       [22.],
       [38.]])

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号,不定期分享干货

背景

由于研发部门给的数据是 csv 文件,业务人员一般都是熟悉 excel 文件,为了方便查看数据,因此需要写个程序,把 csv 文件转换为 excel 文件,由于是经常使用,小编的脚本程序,写成了在命令行中使用的方式

业务人员直接打开 csv 文件会乱码,因excel 默认的编码是 GB2312,所以会乱码

完整脚本

为了方便在命令行中使用,该脚本使用了 argparse 库,如果对该库不是很懂,可以查看相关资料,进行学习

"""
===========================
@Time : 2023/2/1 11:19
@File : csv_to_excel.py
@Software: PyCharm
@Platform: Win10
@Author : DataShare
===========================
"""
import pandas as pd
import argparse

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='csv_to_excel')
    parser.add_argument('--input_file', '-in', type=str, required=True, help='csv文件')
    parser.add_argument('--output_file', '-out', type=str, required=False, default=None, help='excel文件')
    args = parser.parse_args()

    data = pd.read_csv(args.input_file, sep=',', dtype='str', quotechar='"', header=0)

    print('csv文件行数为:', len(data))  # 判断数据行数是否一致,防止不可见字符,例如:回车 等

    if args.output_file is not None:
        if args.output_file.endswith('.xlsx'):
            output_file_converted = args.output_file
        else:
            output_file_converted = args.output_file + '.xlsx'
    else:
        output_file_converted = args.input_file.split('.csv')[0] + '.xlsx'

    # 这是由于Excel单个工作表限制URL类型数据量为65530,超出的部分会被舍弃
    # 只要将strings_to_urls自动转换功能关闭就好了
    writer = pd.ExcelWriter(output_file_converted, engine='xlsxwriter',
                            engine_kwargs={'options': {'strings_to_urls': False}})
    data.to_excel(writer, index=False)
    writer.close()

    print('数据转换完成')

使用教程

前提条件:

  • 需要把以上的完整脚本,复制下来保存为 csv_to_excel.py 文件
  • 本机安装了python,并且在命令行中可以直接使用

使用教程 最好把 csv_to_excel.py 文件与将要转换的 csv 文件放到一个文件夹中

用法1: 只指定需要转换的 csv 文件,转换后的结果 excel 文件,默认与 csv 文件同名,且保存在同一个文件夹里面

python csv_to_excel.py -in test.csv
#python csv_to_excel.py --input_file test.csv

用法2: 指定需要转换的 csv 文件,同时指定输出的 excel 结果文件名

python csv_to_excel.py -in test.csv -out test_convert.xlsx
#python csv_to_excel.py --input_file test.csv --output_file test_convert.xlsx

使用教程

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

经过移动互联网的蓬勃发展后,促使数字化也进入大众视野,现阶段各个行业能数字化的基本都数字化,至于数字化后好用不好用是另一回事了

数字化就会涉及到数据处理、数据存放等,紧接着引出了数据安全,数据存放时是否需要加密的问题,大型公司数据存放在服务器时,敏感数据基本都是加密后存放

小编这里大概梳理了几个常用的加密算法,本篇文章重点是实际使用,不介绍算法原理,算法原理相对比较深奥,涉及到密码学,小编也研究不懂

小编环境

import sys

print('python 版本:',sys.version.split('|')[0])   
#python 版本: 3.11.5

DES、AES加密算法需要利用三方包 pycryptodome

#需要先安装三方包
#pip install pycryptodome

import Crypto

print(Crypto.__version__)   #3.20.0

不同的加解密算法

MD5

MD5是单向加密算法,加密后无法解密,MD5重复(碰撞)概率:三百万亿亿亿亿 分之一 $$(\frac{1}{16})^{32}=(\frac{1}{2})^{128}$$ $$2^{128}=340 2823 66920938 46346337 46074317 68211456$$

import hashlib

data='DataShare 中国'
print(hashlib.md5(data.encode(encoding='UTF-8')).hexdigest())
#6c066bb026f2529e9694420b70b78df2

MD5,加盐

加盐可能初听起来比较奇怪,小编个人感觉应该是翻译不够直白,应该是对数据先进行一些加工,再进行MD5加密

import hashlib

data='DataShare 中国'
salt='12345'

data_salt=data+salt
print(hashlib.md5(data_salt.encode(encoding='UTF-8')).hexdigest())
#2a0e91ca52ec8da75171e9165fc26e28

DES

DES是一种对称加密,所谓对称加密就是加密与解密使用的秘钥是一个,数据加密完成后能解密还原

英语:Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法

ECB模式

  • 加密
from Crypto.Cipher import DES
import binascii

secret='DataShar'  #秘钥必须为8字节
data='DataShare 中国'

#创建一个des对象 ,DES.MODE_ECB 表示模式是ECB模式
des = DES.new(bytes(secret, encoding="utf-8"),AES.MODE_ECB)  
data_encrypt_bin=des.encrypt(bytes(data, encoding="utf-8"))
print(binascii.b2a_hex(data_encrypt_bin).decode())
#0ef72f2ea2eeaff395f2f75fbb76e124
  • 解密
from Crypto.Cipher import DES
import binascii

secret='DataShar'  #秘钥必须为8字节

data_encrypt='0ef72f2ea2eeaff395f2f75fbb76e124'
data_encrypt_bin=binascii.unhexlify(bytes(data_encrypt, encoding="utf-8"))

#创建一个des对象 ,DES.MODE_ECB 表示模式是ECB模式
des = DES.new(bytes(secret, encoding="utf-8"),AES.MODE_ECB) 
print(des.decrypt(data_encrypt_bin).decode())
#DataShare 中国

CBC模式 该模式需要一个偏移量,也就是初始化的值,对数据进行初始化处理,类似MD5加盐

  • 加密
from Crypto.Cipher import DES
import binascii

secret='DataShar'  #秘钥必须为8字节
data='DataShare 中国'
iv = b'12345678'  #偏移量,bytes类型,必须为8字节

des = DES.new(bytes(secret, encoding="utf-8"),DES.MODE_CBC,iv) #创建一个des对象
data_encrypt_bin=des.encrypt(bytes(data, encoding="utf-8"))
print(binascii.b2a_hex(data_encrypt_bin).decode())
#8d5e64e6a20638158d6a2fe43f0cc23d
  • 解密
from Crypto.Cipher import DES
import binascii

secret='DataShar'  #秘钥必须为8字节
iv = b'12345678'  #偏移量,bytes类型,必须为8字节

data_encrypt='8d5e64e6a20638158d6a2fe43f0cc23d'
data_encrypt_bin=binascii.unhexlify(bytes(data_encrypt, encoding="utf-8"))

des = DES.new(bytes(secret, encoding="utf-8"),DES.MODE_CBC,iv) #创建一个des对象
print(des.decrypt(data_encrypt_bin).decode())
#DataShare 中国

AES

高级加密标准(英语:Advanced Encryption Standard,缩写:AES),是美国联邦政府采用的一种区块加密标准,这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用

AES是一种对称加密,所谓对称加密就是加密与解密使用的秘钥是一个,数据加密完成后能解密还原

ECB模式

  • 加密
from Crypto.Cipher import AES
import binascii

secret='DataShareDataSha'  #秘钥必须为16字节或者16字节倍数的字节型数据
data='DataShare 中国'

#创建一个aes对象 ,AES.MODE_ECB 表示模式是ECB模式
aes = AES.new(bytes(secret, encoding="utf-8"),AES.MODE_ECB)  

data_encrypt_bin=aes.encrypt(bytes(data, encoding="utf-8"))
print(binascii.b2a_hex(data_encrypt_bin).decode())
#bae24aeaa8f1258a97edd935ed4ca495
  • 解密
from Crypto.Cipher import AES
import binascii

secret='DataShareDataSha'  #秘钥必须为16字节或者16字节倍数的字节型数据

data_encrypt='bae24aeaa8f1258a97edd935ed4ca495'
data_encrypt_bin=binascii.unhexlify(bytes(data_encrypt, encoding="utf-8"))

#创建一个aes对象 ,AES.MODE_ECB 表示模式是ECB模式
aes = AES.new(bytes(secret, encoding="utf-8"),AES.MODE_ECB)
print(aes.decrypt(data_encrypt_bin).decode())
#DataShare 中国

CBC模式 该模式需要一个偏移量,也就是初始化的值,对数据进行初始化处理,类似MD5加盐

  • 加密
from Crypto.Cipher import AES
import binascii

secret='DataShareDataSha'  #秘钥必须为16字节或者16字节倍数的字节型数据
data='DataShare 中国'
iv = b'0123456789abcdef'  #偏移量,bytes类型,必须为16字节

aes = AES.new(bytes(secret, encoding="utf-8"),AES.MODE_CBC,iv) #创建一个aes对象
data_encrypt_bin=aes.encrypt(bytes(data, encoding="utf-8"))
print(binascii.b2a_hex(data_encrypt_bin).decode())
#6866c9639f59d3485c50d30ec383b70b
  • 解密
from Crypto.Cipher import AES
import binascii

secret='DataShareDataSha'  #秘钥必须为16字节或者16字节倍数的字节型数据
iv = b'0123456789abcdef'  #偏移量,bytes类型,必须为16字节

data_encrypt='6866c9639f59d3485c50d30ec383b70b'
data_encrypt_bin=binascii.unhexlify(bytes(data_encrypt, encoding="utf-8"))

aes = AES.new(bytes(secret, encoding="utf-8"),AES.MODE_CBC,iv) #创建一个aes对象
print(aes.decrypt(data_encrypt_bin).decode())
#DataShare 中国

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

在给业务生成自动化Excel报表时,需要把数据库里面的数据进行处理,然后放到Excel里面并进行美化,用邮件进行发送,满足业务的需求。但是这些自动化程序一般都是部署在Linux服务器上定期进行发送邮件,这样就需要在Linux环境下生成Excel文件,之前有文章介绍过 xlwings库,但是该库不支持Linux,经过查找相关的资料后,openpyxl库 可以满足要求。

xlwings只支持Windows 和 Mac系统 xlwings

openpyxl 三个系统都支持,该库是基于操作XML文件而支持操作Excel文件,支持Excel2010版之后的所有Excel文件 openpyxl

额外话题: 虽然现在一些公司都用BI工具,但是这些BI报表建立后,后期进行维护时需要很大的成本,比如:BI系统有升级、数据库字段有变化、更有甚者创建BI报表的员工离职等等,有的公司可能用的还是Tableau、帆软BI等第三方的工具,更新不及时,无法使用到最新的功能。 个人感觉从数据库里面查询数据,并实现可视化放到Excel文件里面是一个不错的选择。当然一些大厂有充足的人力除外,一些中小型公司用这种方法是最经济、最实用且高效的

深度剖析Excel文件的构成

上面说到openpyxl库是基于操作XML文件而支持操作Excel文件,那么XML文件和Excel有什么关系呢?下面进行介绍: 现阶段公司里面基本用的都是office2013版之后的,文件后缀是 .xlsx ,可以把后缀修改为 .zip ,然后进行解压后,可以发现文件夹里面有一些XML文件,其实Excel就是由XML文件与其他一些文件联合组成的一个文件包,然后经过Excel解析后,呈现在用户面前的是一个可编辑的界面,如果理解了这些,就可以发现微软确实比较牛,他们的产品经理是多么厉害。 excel拆解开

/demo/xl/worksheets/sheet1.xml 文件内容,B1到B3单元格存放的是1-3 sheet.xml

而A1到A3单元格放的是0-2值来代替,真实的值可以在/demo/xl/sharedStrings.xml文件里面查到,这里微软用了映射的方法,放入的是数值,类似pandas里面的category类型一样,可以减少内存占用 sharedStrings.xml

openpyxl介绍

该库是基于PHPExcel,也就是PHP操作Excel文件的库,PHP是后端的开发语言,一般大都是部署在Linux服务器 文档:官方文档 https://openpyxl.readthedocs.io/en/stable/index.html,里面有很详细的介绍,目前只有英文版文档,可以配合百度翻译进行查阅

该库也一直有更新,最近的一次更新时间为:2021年3月9日,python里面建议用一直有更新的库 openpyxl

openpyxl 可以直接生产包含XML文件的文件包,也就是Excel文件,该库也就是按照微软的规则把数据写入XML文件,进而可以生产Excel,达到了操作Excel的目的

使用案例

官方文档:https://openpyxl.readthedocs.io/en/stable/index.html里面有很详细的使用介绍,这里只展示一些自己用过的功能,一下只是部分代码:

from openpyxl import load_workbook
from openpyxl.styles import Font,Border,Side,Alignment,PatternFill

#加载excel文件
workbook = load_workbook(filename = path_excel)

#字体、边框、加粗、居中对齐、格式化设置
bd=Side(border_style='thin', color="A6A6A6") 
for row_num,key in enumerate(result_index):
	worksheet.cell(row_num+2, 1, key).font = Font(name='微软雅黑',bold=False, size=10,color='000000')
	worksheet.cell(row_num+2, 1, key).border = Border(left=bd, top=bd, right=bd, bottom=bd)
	worksheet.cell(row_num+2, 1, key).alignment = Alignment(horizontal='center', vertical='center')

	if '率' in key:
		worksheet.cell(row_num+2, 2).number_format='0.0%'

	if key=='对话轮次':
		worksheet.cell(row_num+2, 2).number_format='0.0'

#单元格填充颜色
worksheet.cell(1, 1, value).fill = PatternFill(fill_type='solid',fgColor="538DD5")

#设置行高、列宽
worksheet.row_dimensions[1].height=29
worksheet.column_dimensions['A'].width=18

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

在网上搜集到的多线程资料都是一些概念介绍,而没有真实的案例告诉读者怎么真实来使用,下面就一个真实的使用场景来介绍多线程。

以下为一些概念引用:

在非python环境中,单核情况下,同时只能有一个任务执行。多核时可以支持多个线程同时执行。但是在python中,无论有多少个核同时只能执行一个线程。究其原因,这就是由于GIL(全局解释器)的存在导致的。

GIL的全称是全局解释器,来源是python设计之初的考虑,为了数据安全所做的决定。某个线程想要执行,必须先拿到GIL,我们可以把GIL看做是“通行证”,并且在一个python进程之中,GIL只有一个,拿不到通行证的线程,就不允许进入CPU执行。GIL只在cpython中才有,因为cpython调用的是c语言的原生线程,所以他不能直接操作cpu,而只能利用GIL保证同一时间只能有一个线程拿到数据。

python针对不同类型的代码执行效率也是不同的: 1、CPU密集型代码(各种循环处理、计算等),在这种情况下,由于计算工作多,ticks技术很快就会达到阀值,然后触发GIL的释放与再竞争(多个线程来回切换当然是需要消耗资源的),所以python下的多线程对CPU密集型代码并不友好。 2、IO密集型代码(文件处理、网络爬虫等设计文件读写操作),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序的执行效率)。所以python的多线程对IO密集型代码比较友好。

主要要看任务的类型,我们把任务分为I/O密集型和计算密集型,而多线程在切换中又分为 I/O切换时间切换(一个线程执行一段时间后,再切换到另外一个线程)。如果任务属于是I/O密集型,若不采用多线程,我们在进行I/O操作时,势必要等待前面一个I/O任务完成,后面的I/O任务才能进行,在这个等待的过程中,CPU处于等待状态,这时如果采用多线程的话,刚好可以切换到进行另一个I/O任务。这样就刚好可以充分利用CPU避免CPU处于闲置状态,提高效率。但是如果多线程任务都是计算型,CPU会一直在进行工作,直到一定的时间后采取多线程时间切换的方式进行切换线程,此时CPU一直处于工作状态,此种情况下并不能提高性能,相反在切换多线程任务时,可能还会造成时间和资源的浪费,导致效能下降。这就是造成上面两种多线程结果不能的解释。

结论:I/O密集型任务,建议采取多线程;对于计算密集型任务,python此时就不适用了。

现实需求

为了解决读取文件、写入文件的I/O密集型任务,现在需要三个线程:
1、读取文件数据的线程-----读线程
2、对读取文件数据进行操作的线程-----数据处理线程
3、把处理结果再写入文件的线程-----写线程
这样就可以解决读取数据等待,写入数据等待的过程

数据准备

建立number.txt,里面放置1~50数字,每行一个 numbet.txt 目的:从number.txt每次读取一行数据,对数据进行处理后,再写入number_write.txt(程序自动建立)

代码

# -*- coding:UTF-8 -*-
# @Time : 2020/7/19 15:38
# @File : 多线程案例.py
# @Software: PyCharm
# @Platform: Win10
# @Author: data_duo

import threading
import os
import time
import queue


def read_txt(path_file_txt):
    with open(path_file_txt, 'r') as file:
        while True:
            data = file.readline().strip()
            if data:
                while True:  # 确保读取的数据一定存入workQueue_read队列,防止队列满了放不下
                    if not workQueue_read.full():
                        workQueue_read.put(data)
                        print(
                            'read_txt线程给workQueue_read队列存入:{} \n'.format(data),
                            end='',
                            flush=True)
                        break
                    else:
                        pass
            else:
                print('退出read_txt线程', end='\n', flush=True)
                break


def handle():
    count = 0
    while True:
        if not workQueue_read.empty():
            data = workQueue_read.get()
            print(
                'handle线程从workQueue_read队列获取:{} \n'.format(data),
                end='',
                flush=True)
            data = int(data) + 1
            data = str(data - 1)
            time.sleep(1)
            count = 0  # 需要重置

            while True:  # 确保数据存入workQueue_write 队列,防止队列满了放不下
                if not workQueue_write.full():
                    workQueue_write.put(data)
                    print(
                        'handle线程给workQueue_write队列存入:{} \n'.format(data),
                        end='',
                        flush=True)
                    break
                else:
                    pass
        else:
            if count < 5:  # 等待workQueue_read队列有数据,一共5次等待机会
                count += 1
                time.sleep(1)
                pass
            else:
                if thread_read.is_alive():
                    pass
                    count = 0  # 需要重置,再次计时
                else:
                    print('退出handle线程 \n', end='', flush=True)
                    break


def write_txt(path_file_txt):
    count = 0
    with open(path_file_txt, 'w+') as file:
        while True:
            if not workQueue_write.empty():
                data = workQueue_write.get()
                print(
                    'write_txt线程从workQueue_write队列获取:{} \n'.format(data),
                    end='',
                    flush=True)
                file.write(data)
                file.write('\n')
                print('write_txt线程写入数据:{} \n'.format(data), end='', flush=True)
                count = 0  # 需要重置,再次计时
            else:
                if count < 5:  # 确保handle线程处理完毕,把数据存入workQueue_write队列
                    count += 1
                    time.sleep(1)
                    pass
                else:
                    if thread_handle.is_alive():
                        pass
                        count = 0  # 需要重置,再次计时
                    else:
                        print('退出写线程 \n', end='', flush=True)
                        break


if __name__ == "__main__":
    threads = []

    workQueue_read = queue.Queue(20)
    workQueue_write = queue.Queue(20)

    print('原来总线程数为:{} \n'.format(threading.active_count()), end='', flush=True)

    # 创建读取线程
    thread_read = threading.Thread(target=read_txt, args=('number.txt',))
    thread_read.start()
    threads.append(thread_read)
    print(
        '启动读取线程后,总线程数为:{} \n'.format(
            threading.active_count()),
        end='',
        flush=True)

    # 创建操作线程
    thread_handle = threading.Thread(target=handle)
    thread_handle.start()
    threads.append(thread_handle)
    print(
        '启动操作线程后,总线程数为:{} \n'.format(
            threading.active_count()),
        end='',
        flush=True)

    # 创建写入线程
    thread_write = threading.Thread(
        target=write_txt, args=(
            'number_write.txt',))
    thread_write.start()
    threads.append(thread_write)
    print(
        '启动写入线程后,总线程数为:{} \n'.format(
            threading.active_count()),
        end='',
        flush=True)

    # 等待所有线程完成
    for t in threads:
        t.join()

    print('退出主线程')

运行结果:

原来总线程数为:1 
启动读取线程后,总线程数为:2 
read_txt线程给workQueue_read队列存入:1 
启动操作线程后,总线程数为:3 
read_txt线程给workQueue_read队列存入:2 
read_txt线程给workQueue_read队列存入:3 
...
read_txt线程给workQueue_read队列存入:20 
启动写入线程后,总线程数为:4 
handle线程从workQueue_read队列获取:1 
read_txt线程给workQueue_read队列存入:21 
handle线程给workQueue_write队列存入:1 
handle线程从workQueue_read队列获取:2 
read_txt线程给workQueue_read队列存入:22 
write_txt线程从workQueue_write队列获取:1 
write_txt线程写入数据:1 
handle线程给workQueue_write队列存入:2 
handle线程从workQueue_read队列获取:3 
...
read_txt线程给workQueue_read队列存入:50 
退出read_txt线程
write_txt线程从workQueue_write队列获取:29 
...
handle线程给workQueue_write队列存入:50 
write_txt线程从workQueue_write队列获取:50 
write_txt线程写入数据:50 
退出handle线程 
退出写线程 
退出主线程

历时相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

在数据处理过程中,多多少少都会和日期时间打交道,比如在分析数据时,一般需要求各年、各季度、各月、各周的销量等,这些基本都会涉及到日期时间,处理日期时间变量是躲不过的

在这里用的名词术语:日期时间,是一个整体

datetime库介绍

官方文档:https://docs.python.org/zh-cn/3/library/datetime.html

datetime包含的类:

  • class datetime.date 日期
  • class datetime.time 时间
  • class datetime.datetime 日期和时间的结合
  • class datetime.timedelta 两个时间之间的间隔
  • class datetime.tzinfo 时区
  • class datetime.timezone tzinfo 的子类

这里主要介绍在工作中经常使用的:

  • class datetime.datetime--------日期时间
  • class datetime.timedelta--------时间间隔

常用函数、方法、属性

  • class datetime.datetime
  1. 获取当前时间,结果为:年、月、日、时、分、秒、微秒 1 秒 = 1000毫秒(millisecond) 1毫秒 = 1000微秒(microsecond)
In [1]: import datetime

In [2]: datetime.datetime.now()
Out[2]: datetime.datetime(2021, 1, 25, 16, 56, 56, 992611)

In [3]: type(datetime.datetime.now())
Out[3]: datetime.datetime
  1. 用指定日期时间创建datetime
In [4]: datetime.datetime(2021,1,1,12,30,00)
Out[4]: datetime.datetime(2021, 1, 1, 12, 30)

In [5]: print(datetime.datetime(2021,1,1,12,30,00))
2021-01-01 12:30:00
  1. 一般日志文件使用的格式
In [6]: dt=datetime.datetime.now().strftime('%Y%m%d%H%M%S')

In [7]: dt
Out[7]: '20210125171518'

一般创建日志文件代码:

import logging
import datetime

#日志记录
def get_logger():
    dt=datetime.datetime.now().strftime('%Y%m%d%H%M%S')
    logger = logging.getLogger('model_main.py')
    logger.setLevel(level = logging.INFO)
    if not os.path.exists('./logs'):
        os.mkdir('./logs')
    handler = logging.FileHandler(f'./logs/{dt}_model_main.txt',encoding='utf-8')
    handler.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    
    return logger, handler
  1. 获取年、月、日等
In [8]: dt = datetime.datetime.now()

In [9]: dt.year
Out[9]: 2021

In [11]: dt.month
Out[11]: 1

In [12]: dt.day
Out[12]: 25

In [13]: dt.hour
Out[13]: 17

In [14]: dt.minute
Out[14]: 20

In [15]: dt.microsecond
Out[15]: 728805
  1. 解析字符串为日期
    strptime:string parse time,官方的符号代表意义:
    https://docs.python.org/zh-cn/3/library/datetime.html#strftime-strptime-behavior
In [16]: datetime.datetime.strptime("1/25/2021 17:27:30","%m/%d/%Y %H:%M:%S")
Out[16]: datetime.datetime(2021, 1, 25, 17, 27, 30)
  1. 把日期格式化为字符串
    strftime:string format time 官方的符号代表意义(同上)
In [17]: dt = datetime.datetime.now()

In [18]: dt
Out[18]: datetime.datetime(2021, 1, 25, 17, 33, 25, 952250)

In [19]: datetime.datetime.strftime(dt,"%Y-%m-%d %H:%M:%S")
Out[19]: '2021-01-25 17:33:25'
  • class datetime.timedelta
    timedelta:time delta 如果学过高等数学的话,那么对delta应该比较熟悉,代表间隔、增量

Delta是第四个希腊字母的读音,其大写为 Δ,小写为 δ。在数学或者物理学中大写的 Δ 用来表示增量符号。 而小写 δ 通常在高等数学中用于表示变量或者符号。 $$Δx = x1 - x2$$

datetime.timedelta 只有 days, seconds 和 microseconds 会保留,其他的单位全部相应会转换为这三个,并且 days, seconds, microseconds 会经标准化处理以保证表达方式的唯一性:
0 <= microseconds < 1000000
0 <= seconds < 360024 (一天的秒数)*
-999999999 <= days <= 999999999

  1. 两个时间相减生成timedelta
In [20]: dt1 = datetime.datetime(2020,12,5,11,12,13)

In [21]: dt2 = datetime.datetime(2021,1,15,12,30,31)

In [22]: dt2 - dt1
Out[22]: datetime.timedelta(days=41, seconds=4698)
  1. 两个时间相隔的天数
    在数据分析中,有时业务要求前后半年时间内都算正常这样的需求,那么就可以用这种方式来解决
In [20]: dt1 = datetime.datetime(2020,12,5,11,12,13)

In [21]: dt2 = datetime.datetime(2021,1,15,12,30,31)

In [23]: dt_delta = dt1 - dt2

In [24]: dt_delta
Out[24]: datetime.timedelta(days=-42, seconds=81702)

In [25]: dt_delta.days
Out[25]: -42

In [26]: abs(dt_delta.days)
Out[26]: 42
  1. 用timedelta类来创建
In [27]: datetime.timedelta(days=50,seconds=27,microseconds=10,milliseconds=29000,minutes=5,hours=8,weeks=2)
Out[27]: datetime.timedelta(days=64, seconds=29156, microseconds=10)

In [28]: datetime.timedelta(10,10,10)
Out[28]: datetime.timedelta(days=10, seconds=10, microseconds=10)
  1. 一个日期加上或减去timedelta增量
    一个时间点加上或减去一个timedelta增量结果还是一个时间点
In [29]: dt = datetime.datetime.now()

In [30]: dt_delta = datetime.timedelta(days = 1)

In [31]: dt + dt_delta
Out[31]: datetime.datetime(2021, 1, 26, 18, 21, 30, 27141)

In [32]: dt - dt_delta
Out[32]: datetime.datetime(2021, 1, 24, 18, 21, 30, 27141)

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

本篇文章为小编翻译文章,小编在查找资料时看到的一篇文章,看了后感觉不错,就翻译过来,供大家参考学习

文章原文地址:
https://www.slingacademy.com/article/python-aiohttp-how-to-download-files-using-streams/

Overview 概述

aiohttp 是一个现代库,为Python提供异步(协程)HTTP客户端和服务器功能。流是一种分块处理数据的方式,无需一次将整个文件加载到内存中,这对于下载大文件或同时处理多个请求非常有用。

可以通过以下步骤下载带有aiohttp流的文件(尤其是几百MB或更多的大文件):

  1. 创建一个 aiohttp.ClientSession 对象,它表示用于发出HTTP请求的连接池(客户端会话,用于发送不同的HTTP请求)

  2. 使用 session.get 方法向文件URL发送get请求,并获得aiohttp.ClientResponse 对象,表示来自服务器的响应

  3. 使用 response.content 属性访问 aiohttp.StreamReader 对象,它是用于读取响应主体的流

  4. 使用 stream.readstream.readany 方法从流中读取数据块,并将其写入本地的文件中

  5. 完成后关闭响应和会话对象(这可以通过使用 async with 语句自动完成)

上面说的这么多,可能让你困惑且难以理解,让我们来看看下面的例子以获得更清晰的理解

Complete Example 完整示例

假如我们要同时下载两个文件,一个文件是CSV,另一个是PDF
CSV文件的URL:https://api.slingacademy.com/v1/sample-data/files/student-scores.csv
PDF文件的URL:https://api.slingacademy.com/v1/sample-data/files/text-and-table.pdf

完整的代码(带说明) 在运行以下代码时,aiohttp 建议更新为最新的版本,否则可能报错

# SlingAcademy.com
# This code uses Python 3.11.4

import asyncio
import aiohttp

# This function downloads a file from a URL and saves it to a local file
# The function is asynchronous and can handle large files because it uses aiohttp streams
async def download_file(url, filename):
    async with aiohttp.ClientSession() as session:
        print(f"Starting download file from {url}")
        async with session.get(url) as response:
            assert response.status == 200
            with open(filename, "wb") as f:
                while True:
                    chunk = await response.content.readany()
                    if not chunk:
                        break
                    f.write(chunk)
            print(f"Downloaded {filename} from {url}")


# This function downloads two files at the same time
# 同时下载两个文件
async def main():
    await asyncio.gather(
        # download a CSV file
        download_file(
            "https://api.slingacademy.com/v1/sample-data/files/student-scores.csv",
            "test.csv",
        ),

        # download a PDF file
        download_file(
            "https://api.slingacademy.com/v1/sample-data/files/text-and-table.pdf",
            "test.pdf",
        ),
    )

# Run the main function
asyncio.run(main())

运行的输出:

Starting download file from https://api.slingacademy.com/v1/sample-data/files/student-scores.csv
Starting download file from https://api.slingacademy.com/v1/sample-data/files/text-and-table.pdf
Downloaded test.pdf from https://api.slingacademy.com/v1/sample-data/files/text-and-table.pdf
Downloaded test.csv from https://api.slingacademy.com/v1/sample-data/files/student-scores.csv

下载的文件将保存在当前的脚本目录中,test.csvtest.pdf,如下面的屏幕截图所示: 下载的文件位置

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

需要把txt文件数据导入mysql数据库,中间需要经过一些数据处理,在经过相关查找后,pandas自带的to_sql(),可以实现把DataFrame直接导入数据库。

虽然mysql有其他的方式导入数据,但是在导入前需要对数据进行一些处理,这些任务无法完成,所以可以借助python来一步实现所有需求。

pandas在处理表格数据有很多优点:API多比较方便、速度快;可循环每行,对每个值进行处理;也可对整列进行处理等

在导入数据库时用的是如下API: Pandas.DataFrame.to_sql()

参数介绍及注意事项

官方文档: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_sql.html

DataFrame.to_sql(nameconschema=Noneif_exists='fail'index=Trueindex_label=Nonechunksize=Nonedtype=Nonemethod=None)

常用参数:

  • name
    导入到mysql时表的名字
    如果mysql里面已经用CREATE TABLE创建好了表,那么就是该表名字
    如果mysql没有创建好表,那么可以自己起一个合适的表名

  • con
    数据库连接,需要安装sqlalchemy库,目前仅支持sqlalchemy库创建的连接,pymysql库创建的连接不支持

engine = create_engine("mysql+pymysql://root:z123456@127.0.0.1:3306/routeapp?charset=utf8")
#SQLALCHEMY_DATABASE_URI = '%s+%s://%s:%s@%s:%s/%s' % (DB_TYPE, DB_DRIVER, DB_USER,DB_PASS, DB_HOST, DB_PORT, DB_NAME)
  • if_exists:以下三个选项,是如果数据库里面已经存在该表的意思
    "fail":直接报错,不再操作,类似mysql创建表时的IF NOT EXISTS才创建表
    "replace":先删除该表,然后再创建
    "append":直接在表后面添加数据

  • index:bool
    是否把DataFrame的索引列写入表中

  • index_label
    如果要把DataFrame的索引列写入表中,那么需要给出该索引列的名字,如果没给的话,那就会用DataFrame的列索引名

注意事项:
con 参数一定要仔细核对,否则数据库会连接失败,可参照上面给出的例子按自己的实际数据库位置进行更改

案例

首先电脑上已安装:mysql软件、sqlalchemy库、pandas库

现在有一些城市之间的火车车次信息,需要导入数据库

import pandas as pd
data=pd.read_table('./data_pandas.txt')
data.head()

城市之间火车信息

假如数据库里面已经创建好该表,并且已经指定好各列的数据类型,现在只需把数据导入到里面

CREATE TABLE IF NOT EXISTS train (
	start_city VARCHAR (100) NOT NULL COMMENT '始发城市',
	start_city_id int COMMENT '始发城市id',
	end_city VARCHAR (100) NOT NULL COMMENT '到达城市',
	end_city_id int COMMENT '到达城市id',
	train_code VARCHAR (20) NOT NULL COMMENT '车次',
	arrival_time VARCHAR (20) NOT NULL COMMENT '到达时间',
	departure_time VARCHAR (20) NOT NULL COMMENT '出发时间',
	run_time INT NOT NULL COMMENT '运行时间(分钟)',
	P1 FLOAT COMMENT '硬座票价',
	P2 FLOAT COMMENT '软座票价',
	P3 FLOAT COMMENT '硬卧票价',
	P4 FLOAT COMMENT '软卧票价',
	P5 FLOAT COMMENT '商务座票价',
	P6 FLOAT COMMENT '一等座',
	P7 FLOAT COMMENT '二等座'
) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '城市之间火车信息';

借助sqlalchemy库来导入数据

from sqlalchemy import create_engine

engine = create_engine("mysql+pymysql://root:z123456@127.0.0.1:3306/routeapp?charset=utf8")

#SQLALCHEMY_DATABASE_URI = '%s+%s://%s:%s@%s:%s/%s' % (DB_TYPE, DB_DRIVER, DB_USER,DB_PASS, DB_HOST, DB_PORT, DB_NAME)

with engine.begin() as conn:
    data.to_sql(name='routeapp_train_line_tb_new_2',con=conn,if_exists='append',index=False)

这里用with语句可以实现mysql的roallback功能,建议最好用with来导入数据 导入数据

参考文章

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

小编最近在处理hive表存储大小时,需要对每个表的大小进行排序,因通过 hadoop fs -du -s -h /path/table 命令获取的数据表大小,其结果是展示为人能直观理解的大小,例如 1.1T、1.9G、49.6M 等,如果想对这些表根据存储大小进行降序排列,利用pandas应该如何做呢? 表大小

小编环境

import sys

print('python 版本:',sys.version.split('|')[0])   
#python 版本: 3.11.5

import pandas as pd

print(pd.__version__)
#2.1.0

测试数据

这里仅列举10行数据,进行演示,小编真实的hive表有几万个 测试数据

函数概述

在pandas对数据进行排序主要使用 pandas.DataFrame.sort_values 方法

DataFrame.sort_values(by, *, 
                axis=0,   
                ascending=True, 
                inplace=False, 
                kind='quicksort', 
                na_position='last', 
                ignore_index=False, 
                key=None)

参数解释:

  • by :str or list of str
    用于排序的单个字段 或 多个字段组成的列表

  • axis:“{0 or ‘index’, 1 or ‘columns’}”, default 0
    排序时的轴向,0 表示行向排序(一行一行排序),1表示列向排序(一列一列排序),默认是 0,也就是Excel中经常使用的排序

  • ascending:bool or list of bool, default True
    升序、降序,默认是升序,也就是True,如果是False,则是降序
    注意:该参数需要和 上面的by参数要相对应

  • inplace:bool, default False
    是否原地更新排序的数据,默认是False,表示调用该方法后,会返回一个新的数据框

  • kind:{‘quicksort’, ‘mergesort’, ‘heapsort’, ‘stable’}, default ‘quicksort’
    进行排序时,指定的排序算法,默认是 quicksort,快速排序算法

  • na_position:{‘first’, ‘last’}, default ‘last’
    在排序的数据中,指定 NaN 的排序位置,默认是排在最后

  • ignore_index:bool, default False
    是否要忽略数据的索引,默认是 Fasle,不忽略,使用数据原本的索引

  • key:callable, optional
    排序之前使用的函数,该函数需要是矢量化的,也就是传入参数是 Series ,返回的结果也需要为 Series ,该函数会逐个用在被排序的字段上 key参数

官方文档https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.sort_values.html

完整案例

import pandas as pd

data=pd.read_excel('排序数据.xlsx',sheet_name='排序')

key_type={'T':1,'G':2,'M':3,'K':4}

data.sort_values(by=['大小2','大小1'],
                 ascending=[True,False],
                 key=lambda col: col.map(key_type) if col.name=='大小2' else col
                )

排序结果

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

在数据处理过程中经常遇到求两个字典相加(并集),要求相同的键,值相加,不同的键,进行汇集,最后得出一个总的字典,自己可以先进行思考,利用已有的知识,是否马上在心里有解决方法

例如:
d1={'a':1,'b':1}
d2={'b':2,'c':2}
要求的结果:d1+d2={'a':1,'b':3,'c':2}

下面介绍两种方法:

  • 自定义函数法
  • 利用collections库

方法一

def sum_dict(a,b):
    temp = dict()
    # dict_keys类似set; | 并集
    for key in a.keys() | b.keys():
        temp[key] = sum([d.get(key, 0) for d in (a, b)])
    return temp

案例: 自定义函数法

方法二

利用collections库里面的Counter函数进行计数,collections模块是一个很强大的模块,里面有各种扩展的变量容器
如果感兴趣的话可以参考这篇文章 Python-collections模块,里面有详细的使用方法 collections.Counter

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注 DataShare (同微),不定期分享干货

问题

请认真思考下这个问题,应该会输出什么呢? 问题

答案

b. [1, 2, 3, 4] 答案

解释

第1行:创建一个列表,变量 x 指向这个列表

x = [1, 2, 3]

第2行:把变量 x 赋值给一个新变量 y,这两个变量同时指向了第1行创建的列表

y = x 第2行解释

第3行:在列表后面追加一个新元素 4,这时 xy 同时也更改为新列表的值

x.append(4)

第3行解释

第4行:变量 y 与 新列表 [5] 进行相加运算,会生成一个新列表,这个新列表与原来的列表不一样,变量 y 指向这个新生成的列表

y = y + [5] 第4行解释

知识点:
列表的 apend 方法是在原有的内存地址后面继续添加新元素,而加号运算符则会产生新的列表,存放在新的内存地址里面

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

字符串格式化的主要使用场景是让变量打印出来,让人看着美观、易于查看。有时会直接print出来;有时会把这些内容写到文件里面,也就是进行日志记录。比如日志文件,设置好格式,后期在查询问题时,就可以快速定位。

字符串格式化就类似于手机APP界面一样,UI、排版设计都是为了使人机交互更加直观,内容让人看起来更美观、更舒服。

字符串格式化方法

  • 方法 1

在 python 2.6 之前,利用 百分号% 占位符,进行格式化

>>> name = '张三'
>>> print('哈喽,%s'%name)
哈喽,张三
  • 方法 2 ---------- 现阶段使用最多的方法

Python2.6 引入,性能比 % 更强大,字符串的 format 方法

>>> name = '张三'
>>> '哈喽,{}'.format(name)
'哈喽,张三'
  • 方法 3 ---------- 推荐使用的方法

为了进一步简化格式化方法,Eric Smith 在2015年提交了 PEP 498 -- Literal String Interpolation 提案。字符串开头加上一个字母 f ,是在 Python3.6 新加入的字符串格式化方法

>>> name = '张三'
>>> f'哈喽,{name}'
'哈喽,张三'

推荐大家用最新的方法

推荐方法常规用法

设定浮点数精度

需要加一个 :(冒号)再加一个 .(英文句号)然后跟着小数点位数最后以f(float)结尾

num = 3.1415926   #山巅一寺一壶酒
print(f'圆周率保留两位小数为:{num:.2f}')

#圆周率保留两位小数为:3.14
数字格式化为百分数

方法与浮点数格式化类似,但是要用%代替结尾的f

a = 1
b = 3

c = a / b

print(f'百分数为:{c:%}')
#百分数为:33.333333%

print(f'百分数保留两位小数为:{c:.2%}')
#百分数保留两位小数为:33.33%
格式化 datetime 对象

支持的格式详见官方文档: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes

import datetime

now = datetime.datetime.now()
print(f'{now:%Y-%m-%d %H:%M:%S}')
#2021-01-19 16:44:32
字符串前补零

{var:0len} 方法来进行字符串补零,len是最终返回字符串的长度

num = 123
print(f"{num:05}")
#00123
字符串居中

想要实现字符串居中,可以通过 var:^N 的方式。其中var是你想要打印的变量,N是字符串长度。如果N小于var的长度,会打印全部字符串。

test = 'hello world'

print(f'{test:^20}')
#    hello world     

print(f'{test:*^20}')
#****hello world*****

print(f'{test:^2}')
#hello world
进制转换
print(f'{7:b}')
#111

bases = {"b": "bin", 
         "o": "oct", 
         "x": "hex", 
         "X": "HEX", 
         "d": "decimal"}

for n in range(1,21):
    #print(n)
    for base,desc in bases.items():
        print(f'{desc}:{n:5{base}}',end=' '*5)
    print()

进制转换

参考文章

  • https://miguendes.me/73-examples-to-help-you-master-pythons-f-strings
  • https://mp.weixin.qq.com/s/0F06lMbJSqN2msX4bNl2Aw

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

1.全局设置

#显示所有列
pd.set_option('display.max_columns', None)

#显示所有行
pd.set_option('display.max_rows', None)

#设置value的显示长度为100,默认为50
pd.set_option('max_colwidth',100)

#禁止自动换行(设置为Flase不自动换行,True反之)
pd.set_option('expand_frame_repr', False)

#打开修改时复制机制
pd.set_option("mode.copy_on_write", True)

#打印时显示全部数组信息
np.set_printoptions(threshold=np.inf)

2.画图

#内嵌画图
%matplotlib inline

#单独画图
%matplotlib qt

3.显示

#让一个cell同时有多个输出print
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all" 

以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

每门编程语言都有其独特的用途,目前python在数据科学方面发展的相对比较全面,大家目前也都喜欢使用python来处理数据、做模型开发等。python在数据处理方面离不开 pandas 库,该库在今年的4月3日发布了 2.0版更新,对底层进行了大量的重构以优化性能和稳定性

主要新增功能及优化

1、引擎增加pyarrow

最主要是底层的数据引擎增加了对pyarrow支持(Apache Arrow 内存数据交换格式),pandas之前的底层引擎是numpynumpy在处理数值型数据时效率很高,但是在处理字符串型的数据时效率比较慢,pyarrow的引入,使字符串的处理效率得到明显提升

2、写入时复制(Copy-on-Write)的优化

当你复制一个pandas对象,如DataFrameSeries,而不是立即创建一个新的数据副本,pandas将创建一个对原始数据的引用(视图),推迟创建一个新的副本,直到你以某种方式修改数据时才创建一个副本,而原数据保持不变,

这可以大大减少内存的使用,提高性能,因为你不需要对数据进行不必要的复制。总的来说,写时拷贝是一种强大的优化技术,可以帮助你更有效地处理大型数据集,并减少内存占用

安装

必须要安装pyarrow库,否则运行时会报错

pip install --upgrade pandas    #更新pandas库
pip install pyarrow             #安装pyarrow库

安装

测试

测试数据是有12W+行数据

1、加载数据测试

从加载csv数据可以看出,速度有明显的提升,差不多有10倍 加载数据测试

2、字符串处理测试

从字符串处理方面看,速度大约是原来的40倍 字符串处理测试

3、修改时复制机制

  • 默认该机制是关闭的,修改视图数据时,原始数据也会被修改 默认

  • 打开修改时复制机制,修改视图数据时,原始数据保持不变 打开机制

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

最近在项目处理数据时,对pandas里面的数据类型进行转换时(astype),产生了一些意外的情况,经过研究,对数据框里面的数据类型,又有了新的认识,分享出来供大家参考学习。

创建模拟数据

模拟数据

  • 假如模拟的数据如上图所示,里面有一些空单元格,下面读取模拟数据
import pandas as pd
import numpy as np

data=pd.read_excel('111.xlsx',sheet_name='astype')

data

读取模拟数据

  • 查看整体数据类型,可以看出所有的数据类型均为object,这里的object对应的是python里面的str字符类型
data.dtypes

数据类型

数据类型对应

  • 查看字段4每一个数据是什么类型
for i in data['字段4']:
    print(i,'\t',type(i))

每个数据类型

可以看出字段4这一列里面,有strfloatint三种数据类型,这里就可以看出一列里面数据类型可以不同,类似Excel一列,每个单元格可以存放不同类型的数据,和数据库里面一列完全不一样,数据库里面一列数据类型在建表时,已声明类型,只存放一种类型。但是上面在获取整列数据类型时返回的是object,用的是最大的数据类型,能囊括整列的数据类型

如果astype类型强制转换

data['字段4_astype']=data['字段4'].astype('str')

data

for i in data['字段4_astype']:
    print(i,'\t',type(i))

类型强制转换

可以看出这里全部转换为strNaN也会强制转换为字符型nan,不再是np.nan nan

这样的话就出现一个问题,astype是强制把所有的类型都转换为str,而不忽略NaN,要对非NAN进行转换,就需要自定义函数来实现

自定义函数实现非NAN转换类型

def astype_str_notna(df):
    '''
    传入参数:数据框里面一列  Series
    
    return:转换后的一列  Series
    '''
    t=[]
    for i in df:
        if type(i)== float:
            if not np.isnan(i):
                i=str(int(i))
        if type(i)== int:
            i=str(i)
            
        t.append(i)
     
    
    return pd.Series(t)

data['字段4_def']=astype_str_notna(data['字段4'])

data

data['字段4'].isna()

自定义函数

通过自定义函数,可以实现数据类型转换,而忽略NAN,从而达到在数据统计时,不会计算NAN

数据统计


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

数据筛选背景

在处理数据时,我们可能希望从数据库里面筛选出符合特定条件的记录(个案或样本,不同的行业叫法不一样),平常大家对Excel筛选很熟悉,比如从A字段(变量或特征)包含“团队”,B字段大于等于40,筛选出符合这两个条件的记录,如下图所示:

excel数据筛选


pandas处理

  • 正确代码
#加载库
import pandas as pd
import numpy as np

#读取数据
data=pd.read_excel('test.xlsx')

#查看数据类型
data.dtypes

#查看数据前5行
data.head()

#根据条件筛选出数据
data.loc[(data['A'].str.contains('团队')) & (data['B']>=40)]
#data[(data['A'].str.contains('团队')) & (data['B']>=40)]    #这两行都可以

结果示例

  • 错误代码
  1. 位运算符&|,而不是逻辑运算符 andor,两者是有区别的
data.loc[(data['A'].str.contains('团队')) and (data['B']>=40)]

位运算符

  1. 缺少括号()导致筛选不出数据,但是不报错
data.loc[data['A'].str.contains('团队') & data['B']>=40]

缺少括号

经过多方查找原因,动手实践,这个问题貌似没有帖子仔细进行解释,那这是为什么筛选不出来数据呢,加了括号就可以,可能有同学一下子就明白了,运算符的顺序,对,位运算符&的优先级 高于 比较运算符>=,其实也就是只对后面的条件加上括号即可,但是考虑到逻辑严谨性,最好把所有条件都括起来,详情参考这篇文章Python 3 的运算符及优先级


pandas赋值操作

  • 正确代码

注意避免链式操作导致SettingwithCopyWarning ,最详细的解释,请参考这篇文章Pandas 中 SettingwithCopyWarning 的原理和解决方案

#赋值操作
data.loc[(data['A'].str.contains('团队')) & (data['B']>=40),'C']='是'

data

赋值操作

及时原来的数据框里面没有C列变量,但是在赋值时可以直接指定,数据框会自动生成这一列变量

  • 错误代码
  1. 缺少.loc导致报错提示不可哈希,对这个不是很懂
data[(data['A'].str.contains('团队')) & (data['B']>=40),'C']='是'

缺少 .loc 建议为了使代码规范,在根据条件筛选时就把.loc带上,防止后期出错

  1. 链式操作,提示SettingwithCopyWarning
data.loc[(data['A'].str.contains('团队')) & (data['B']>=40)]['C']='是'

data

链式操作

为什么会出现这种情况,请详细阅读这篇文章Pandas 中 SettingwithCopyWarning 的原理和解决方案


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

在数据处理时,有时需要对数据进行分列,类似于Excel里面的分列功能,这个在pandas里面也可以实现,下面就来详细介绍相关的方法及注意点,前提是你已经对pandas有一定的了解

导入数据

这里介绍的是从Excel导入数据,当然也可以从其他文件导入、数据库查询后导入等,为了弄清楚里面的细节,本教程从Excel导入数据

import pandas as pd
import numpy as np

data=pd.read_excel('split.xlsx')

查看原始数据及各列数据类型,可以看到指标、选项都是object类型,其中选项列没有缺失值 原始数据.png

对选项列进行分列

对导入的原始数据进行分列,这里运用的是pandas.Series.str.split方法,可以理解为把Series作为字符串进行分列操作,分列都是对字符串进行操作的

split_data=data['选项'].str.split(':',expand=True)   #需要添加expand=True,使分列后的数据扩展为一个数据框
split_data

原始数据分列.png

可以明显看到分列后的数据,第1、5、6索引行全是缺失值, 对比上面的原始数据,这些都是只有一个数字,难道分列方法split对只有一个数字不能分列吗?其实则并不然,实际的原因请往下看

寻找原因

查看Excel里面的数据寻找原因,发现选项所在列,单个数字在Excel单元格是数字,其他的都是文本,因Excel里面数字一般都是在单元格里面都是靠右对齐,而文本都是靠左对齐 Excel数据.png

但是pandas导入数据后,已经查看了选项列为object类型,难道判断的数据类型有问题? 请继续往下看

强制转换数据类型,再次分列

data['选项']=data['选项'].astype('str')
#data['选项']=data['选项'].astype('object')     #这两个代码都可以转换

split_data=data['选项'].str.split(':',expand=True)
split_data

数据类型转换后再分列.png 可以看到已经成功进行分列了,说明pandas读取的数据,判断出来的数据类型并不一定是这一列所有数据的真实类型,而是能概括所有类型的一个较大的类型(兼容所有类型),并没有强制转换为同一个数据类型,比如选项列,里面有数值型、字符串型,那么较大的一个类型是object,pandas及认为该列数据类型是object

合并数据

split_data.columns=['s_1','s_2','s_3','s_4']
data.join(split_data)   #join比较方便,根据索引直接对两个表进行链接,而merge需要设置链接时的字段

成功分列后数据.png

分列时注意事项

  1. 导入数据后一定要检查数据类型,不要急着去处理
  2. 分列前检查该列数据类型,确保该列数据类型都是字符串类型,或者object类型,当数据量很大的时候这个很容易出错

pandas里面数据类型对照

详情请参考这篇博文,数据处理过程的数据类型

数据类型.jpg

背景

最近在处理别人给的数据时,大概 700w+ 行数据,发现有的字段里面存在换行符、斜杠等一些特殊字符,于是想着就用Python来处理下,因为Pandas处理数据还是很方便,结果发现这些转义字符: \n\r\ 一直替换不掉,后来经过研究成功替换,分享出来供大家参考

演示数据

演示数据

python 与 pandas版本

特意说一下版本,现在不同的版本功能可能不一样 软件版本

问题复现

因为涉及到字符串替换,所以直接想到的方法是用 pandas.Series.str.replace 来进行替换,但是一直不能成功,即使双斜杠也不行 问题复现

问题解决

在上面第7个单元格运行时,报了一个错误,FutureWarning: The default value of regex will change from True to False in a future version.

于是就翻看了 pandas.Series.str.replace 的官方文档:

http://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.str.replace.html

文档中写了 regex:bool, default True,正则默认是开启的

个人经验:正则表达式来处理转义字符是比较麻烦的事,因为里面的斜杠不知道要写几个,总是记不住

官方文档


经过测试如下几种方法,可行:

方法1:

明确使用正则来处理,并且使用原字符 r 方法1

方法2:

明确使用正则来处理,正常的写法,经测试需要4个斜杠 方法2

方法3:

不使用正则来处理,但是在处理单个斜杠时,必须用2个 方法3

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

pandas在数据处理过程中,除了对整列字段进行处理之外,有时还需求对每一行进行遍历,来处理每行的数据。本篇文章介绍 2 种方法,来遍历pandas 的行数据

小编环境

import sys

print('python 版本:',sys.version.split('|')[0])   
#python 版本: 3.11.5

import pandas as pd

print(pd.__version__)
#2.1.0

演示数据

演示数据

方法1

pandas.DataFrame.itertuples:返回的是一个命名元组

官方文档:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.itertuples.html

1. 无任何参数

import pandas as pd
data=pd.read_excel("data.xlsx")

for row in data.itertuples():
    print("row:",row,"\n")
    #row: Pandas(Index=0, 序号=1, 分割字符='1&1&1', 固定宽度='111') 
    
    print("type(row):",type(row),"\n")
    #type(row): <class 'pandas.core.frame.Pandas'> 
    
    print("row.序号:",row.序号)
    #row.序号: 1
    
    print("row.分割字符:",row.分割字符)
    #row.分割字符: 1&1&1
    
    print("row.固定宽度:",row.固定宽度)
    #row.固定宽度: 111
    
    break

2. 忽略掉索引

import pandas as pd
data=pd.read_excel("data.xlsx")

for row in data.itertuples(index=False):  #忽律索引
    print("row:",row,"\n")
    #row: Pandas(序号=1, 分割字符='1&1&1', 固定宽度='111') 
    
    print("type(row):",type(row),"\n")
    #type(row): <class 'pandas.core.frame.Pandas'> 
    
    print("row.序号:",row.序号)
    #row.序号: 1
    
    print("row.分割字符:",row.分割字符)
    #row.分割字符: 1&1&1
    
    print("row.固定宽度:",row.固定宽度)
    #row.固定宽度: 111
    
    break

3. 对命名元组起别名

import pandas as pd
data=pd.read_excel("data.xlsx")

for row in data.itertuples(index=False,name="data"):
    print("row:",row,"\n")
    #row: data(序号=1, 分割字符='1&1&1', 固定宽度='111')  
    
    print("type(row):",type(row),"\n")
    #type(row): <class 'pandas.core.frame.data'> 
    
    print("row.序号:",row.序号)
    #row.序号: 1
    
    print("row.分割字符:",row.分割字符)
    #row.分割字符: 1&1&1
    
    print("row.固定宽度:",row.固定宽度)
    #row.固定宽度: 111
    
    break

方法2

pandas.DataFrame.iterrows:返回 (index, Series) 元组

官方文档:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.iterrows.html

import pandas as pd
data=pd.read_excel("data.xlsx")

for index,row in data.iterrows():
    print("index:",index,"\n")
    #index: 0
    
    print("row:",row,"\n")
    #row: 序号          1
    #分割字符    1&1&1
    #固定宽度      111
    #Name: 0, dtype: object
    
    print("type(row):",type(row),"\n")
    #type(row): <class 'pandas.core.series.Series'> 
    
    print("row['序号']:",row['序号'])
    #row['序号']: 1
    
    print("row['分割字符']:",row['分割字符'])
    #row['分割字符']: 1&1&1
    
    print("row['固定宽度']:",row['固定宽度'])
    #row['固定宽度']: 111
    
    break

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

数据分列在数据处理中很常见,数据分列一般指的都是字符串分割,这个功能在Excel里面很实用,处理数据非常方便,那么在pandas数据框中怎么使用呢,今天这篇文章就来详细介绍下 分列

模拟数据

以下面这9行数据作为案例来进行处理 模拟数据

读取数据

#加载库
import pandas as pd

#读取数据
data=pd.read_excel('data.xlsx')

读取数据

分割符号分列

主要运用了pandas里面列的str属性,str 有很多的方法,感性的同学可以自动查找,这里不做过多介绍。分割字符用到的就是split方法

重点:在分割前一定要把该列强制转换为字符型

#对指定列进行分割
split_data_1=data['分割字符'].astype('str').str.split('&',expand=True)

#修改分割后的字段名称
split_data_1.columns=['D_'+str(i) for i in split_data_1.columns]

#与原始数据进行合并
data_result=data.join(split_data_1)

分割符号

固定宽度分列

pandas里面没有固定分割的相应函数,这里巧妙的运用了辅助函数来进行处理,这里的固定宽度为1

#定义个辅助函数
def concat_split(x,width=1):
    result=''
    start=0
    while True:
        s=str(x)[start:start+width]
        
        if s:
            result =result + s + '&'
        else:
            break
        start=start+width
        
    return result[:-1]

#先利用辅助函数,再进行分割
split_data_2=data['固定宽度'].map(concat_split).str.split('&',expand=True)

#修改分割后的字段名称
split_data_2.columns=['W_'+str(i) for i in split_data_2.columns]

#与原始数据进行合并
data_result=data.join(split_data_2)

固定宽度

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

value_counts介绍

value_counts是一种查看表格某列中有多少个不同值的快捷方法,并计算每个不同值有在该列中个数,类似Excel里面的count函数

其是pandas下面的顶层函数,也可以作用在Series、DataFrame下

pd.value_counts(
    values,
    sort=True,        #是否排序,默认是要排序
    ascending=False,     #默认降序排列
    normalize=False,     #标准化、转化成百分比形式
    bins=None,    #可以自定义分组区间,默认是没有,但也可以自定义区间
    dropna=True,   #是否删除nan,默认删除
)

常规用法:

import pandas as pd

pd.value_counts()
df.value_counts()
df['字段'].value_counts()

#创建模拟数据

>>> import pandas as pd
>>> data=pd.DataFrame({'字段1':[1,2,3,4,5,6,5,3,2,4,5,4,4,4,6],
                   '字段2':['A','B','B','A','A','A','B','B','B','C','C','C','C','B','B']})
>>> data
    字段1 字段2
0     1   A
1     2   B
2     3   B
3     4   A
4     5   A
5     6   A
6     5   B
7     3   B
8     2   B
9     4   C
10    5   C
11    4   C
12    4   C
13    4   B
14    6   B

>>> data.dtypes
字段1     int64
字段2    object
dtype: object

Series情况下:

pandas 的 value_counts() 函数可以对Series里面的每个值进行计数并且排序,默认是降序

>>> data['字段2'].value_counts()
B    7
C    4
A    4
Name: 字段2, dtype: int64

>>> data['字段1'].value_counts()
4    5
5    3
6    2
3    2
2    2
1    1
Name: 字段1, dtype: int64

可以看出,既可以对分类变量统计,也可以对连续数值变量统计

如果是要对结果升序排列,可以添加ascending=True来改变

>>> data['字段2'].value_counts(ascending=True)
A    4
C    4
B    7
Name: 字段2, dtype: int64

如果不想看统计的个数,而是想看占比,那么可以设置normalize=True即可,结果是小数形式

>>> data['字段2'].value_counts(normalize=True)
B    0.466667
C    0.266667
A    0.266667
Name: 字段2, dtype: float64

DataFrame情况下

可以通过apply,对每一列变量进行统计

>>> data.apply(pd.value_counts)
   字段1  字段2
1  1.0  NaN
2  2.0  NaN
3  2.0  NaN
4  5.0  NaN
5  3.0  NaN
6  2.0  NaN
A  NaN  4.0
B  NaN  7.0
C  NaN  4.0

通过pandas进行调用

>>> pd.value_counts(data['字段2'])
B    7
C    4
A    4
Name: 字段2, dtype: int64

以上是自己实践中遇到的一些点,分享出来供大家参考学习,欢迎关注DataShare公众号

背景

最近在处理用户评论数据时,从Linux服务器上面用pandas导出的csv文件,下载到自己的Windows电脑,再用本地pandas读取时发现数据行数不一致的情况,比如在Linux服务器上面数据一共有10行,但是用自己本地电脑pandas读取时确大于10行。

问题出现的具体场景

公司Linux服务器上面安装的有Jupyter notebook,在自己本地电脑输入网址是可以直接访问并使用,而且很方便上传、下载文件,对于Linux服务器小白来说很方便,省去了ssh连接Linux服务器的过程。 遇到的这个问题是通过本地电脑连接到Linux服务器Jupyter notebook处理了一些数据(用户评论文本数据),然后导出到csv文件,下载到自己的Windows电脑,然后使用本地的python环境读取数据,发现数据行数不一致的问题。

问题查找

首先找出了从哪一行开始出现串行,查看具体的文本数据,发现在文本数据里面出现特殊转义字符\r,于是豁然开朗,Linux的换行符为\n,而Windows的换行符为\r\n,所以在文本里面出现\r字符时,与Windows换行符有冲突,pandas读取数据时出现数据行数不一致问题。

解决方法

在pandas读取csv数据时,可利用参数lineterminator,明确指定该参数后,可以解决该问题

pd.read_csv('test.csv',lineterminator='\n')

具体可以看看pandas.to_csv这个参数的解释 pandas.to_csv

案例复现

Linux服务器上面的数据 linux服务器上面的数据

下载后用Windows来读取该数据 windows读取数据

可以看出,如果不加 lineterminator 参数的话,数据行数会不一致,加了参数后,数据行数保持一致。由于Linux与Windows两个系统的换行符不一样,因此大家在处理数据时可以利用 lineterminator 参数来避免这样的问题,分享出来供大家参考

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

平时用时知道有相应的设置及相应的原理,具体设置时又不好查找,现特此整理出来供大家收藏

可左右滑动查看代码

Anaconda

pip list
#或者
conda list
#其中,pip list 只能查看库,而 conda list 则可以查看库以及库的版本


pip install scipy
pip install scipy --upgrade
# 或者
conda install scipy
conda update scipy

# 更新所有库
conda update --all

# 更新 conda 自身
conda update conda

# 更新 anaconda 自身
conda update anaconda

jupyter

#显示所有列
pd.set_option('display.max_columns', None)

#显示所有行
pd.set_option('display.max_rows', None)

#设置value的显示长度为100,默认为50
pd.set_option('max_colwidth',100)

#内嵌画图
%matplotlib inline

#单独画图
%matplotlib qt

#画图中文乱码、负号
plt.rcParams['font.sans-serif']=['Microsoft YaHei']
plt.rcParams['axes.unicode_minus']=False

#linux指定字体
from matplotlib.font_manager import FontProperties
zhfont = FontProperties(fname="/home/public/font/SimHei.ttf", size=14) 
plt.xlabel('日期',fontproperties = zhfont,fontsize=14)
plt.xticks(fontproperties=zhfont)

#让一个cell同时有多个输出print
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all" 

主要的数据分析包

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.figure import SubplotParams  
#我们使用SubplotParams 调整了子图的竖直间距
#plt.figure(figsize=(12, 6), dpi=200, subplotpars=SubplotParams(hspace=0.3))

import scipy.stats as stats
import seaborn as sns
import statsmodels.api as sm

Sklearn

from sklearn import datasets    #本地数据
from sklearn.model_selection import train_test_split    #进行数据分割

from sklearn.feature_extraction import DictVectorizer  #特征抽取和向量化
from sklearn.preprocessing import PolynomialFeatures   #多项式特征构造

from sklearn.feature_selection import VarianceThreshold  #基于方差特征选择
from sklearn.feature_selection import SelectKBest,SelectPercentile  #特征选择
#For classification: chi2, f_classif, mutual_info_classif
#For regression: f_regression, mutual_info_regression
from sklearn.feature_selection import RFE   #递归特征消除 (Recursive Feature Elimination)
from sklearn.feature_selection import SelectFromModel   #基于模型选择特征

from sklearn.decomposition import PCA  #主成分分析
from sklearn.manifold import MDS  #多维尺度分析
from sklearn.manifold import TSNE  #T分布和随机近邻嵌入

from sklearn.pipeline import Pipeline       #管道
from sklearn import metrics      #模型评估
from sklearn.model_selection import GridSearchCV  #网格搜索交叉验证
from sklearn.model_selection import KFold  #K折交叉验证
from sklearn.model_selection import cross_val_score  #交叉验证

from sklearn.linear_model import LinearRegression	#线性回归

from sklearn.linear_model import LogisticRegression  #逻辑回归

from sklearn import svm    #支持向量机

from sklearn.tree import DecisionTreeClassifier  #决策树
from sklearn.ensemble import RandomForestClassifier  #随机森林
from sklearn.ensemble import GradientBoostingClassifier  #梯度提升树

from sklearn.naive_bayes import MultinomialNB  #多项式朴素贝叶斯
from sklearn.naive_bayes import BernoulliNB  #伯努利朴素贝叶斯
from sklearn.naive_bayes import GaussianNB  #高斯朴素贝叶斯

from sklearn.neighbors import KNeighborsClassifier  #k紧邻

from sklearn.cluster import KMeans   #k均值聚类
from sklearn.cluster import DBSCAN  #基于密度的空间聚类
from sklearn.cluster import SpectralClustering  #谱聚类
from sklearn.cluster import Birch  #层次聚类

from sklearn.externals import joblib  #保存模型

pycharm脚本模板

"""
===========================
@File  : ${NAME}
@Author: DataShare
@Date  : ${DATE} ${TIME}
===========================
"""
  • 确定文件编码
    当不知道别人给的txt文件不知道是什么编码时,可以通过chardet模块来判断是属于什么编码 chardet模块是第三方模块,需要手动安装
import chardet

data= open('111.TXT','rb').readline()         
#读取一行数据即可,不用全部读取,节省时间,'rb' 指定打开文件时用二进制方法

print(data)      #预览一下二进制数据
chardet.detect(data)     #判断编码

111.png

由输出结果可以判定,该txt是'GB2312'编码概率是99%,confidence: 0.99 ,所以可以确定该txt编码格式就是'GB2312'

  • 用判断出来的编码打开txt文件
f = open('111.TXT','r',encoding='gb2312')        #gb2312<gbk<gb18030
data=f.readlines()           #把数据读取到列表里面
f.close()

222.png

这时出现错误,为什么已经判断文件就是'GB2312',打开还是报错呢???

难道是判断的编码错误的,然后再去读取原txt文件,多读取了一些数据再判断是什么编码,结果还是'GB2312',这是为什么呢???

  • 设置忽略非法字符参数

查看了open函数的参数后,里面有个errors参数,有三个级别可选,一般选择ignore即可

333.png

  • 再次去打开文件

设置errors='ignore'后,成功打开文件

f = open('111.TXT','r',encoding='gb2312',errors='ignore')        
#忽略非法字符  gb2312<gbk<gb18030

data=f.readlines()           #把数据读取到列表里面
f.close()
  • 思考:为什么会有不能识别的字符呢
  1. 网络爬取的文字,里面有一些表情、其他语言,例如:韩语、日语,不是中文的所能包含的,在再次解码时可能会报错
  2. 由于文件比较大,在文件拷贝时由于磁盘原因,可别字符被修改或遗漏
  3. 网络爬取时,信息包里面的字符错误,众所周知信息在传输时是1或0,在网线里面是波形或者激光,如果在较远传输过程中,有可能会丢失信息等一些情况,能确保99%的信息量已经很好了

参考

  1. https://www.cnblogs.com/sesshoumaru/p/6047046.html
  2. https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001510905171877ca6fdf08614e446e835ea5d9bce75cf5000
  3. https://baike.baidu.com/item/bytes/6743146?fr=aladdin

以上是自己在处理数据时遇到的一些阻碍,分享出来供大家参考,欢迎指正与交流

背景

数据的合并与关联是数据处理过程中经常遇到的问题,在SQL、HQL中大家可能都有用到 join、uion all 等 ,在 Pandas 中也有同样的功能,来满足数据处理需求,个人感觉Pandas 处理数据还是非常方便,数据处理效率比较高,能满足不同的业务需求

本篇文章主要介绍 Pandas 中的数据拼接与关联

数据拼接---pd.concat

concat 是pandas级的函数,用来拼接或合并数据,其根据不同的轴既可以横向拼接,又可以纵向拼接

函数参数

pd.concat(
    objs: 'Iterable[NDFrame] | Mapping[Hashable, NDFrame]',
    axis=0,
    join='outer',
    ignore_index: 'bool' = False,
    keys=None,
    levels=None,
    names=None,
    verify_integrity: 'bool' = False,
    sort: 'bool' = False,
    copy: 'bool' = True,
) -> 'FrameOrSeriesUnion'
  • objs:合并的数据集,一般用列表传入,例如:[df1,df2,df3]
  • axis:指定数据拼接时的轴,0是行,在行方向上拼接;1是列,在列方向上拼接
  • join:拼接的方式有 inner,或者outer,与sql中的意思一样

以上三个参数在实际工作中经常使用,其他参数不再做介绍

案例: 模拟数据

  • 横向拼接 横向拼接-1 字段相同的列进行堆叠,字段不同的列分列存放,缺失值用NAN来填充,下面对模拟数据进行变换用相同的字段,进行演示

横向拼接-2

  • 纵向拼接 纵向拼接

可以看出在纵向拼接的时候,会按索引进行关联,使相同名字的成绩放在一起,而不是简单的堆叠

数据关联---pd.merge

数据联接,与SQL中的join基本一样,用来关联不同的数据表,有左表、右表的区分,可以指定关联的字段

函数参数

pd.merge(
    left: 'DataFrame | Series',
    right: 'DataFrame | Series',
    how: 'str' = 'inner',
    on: 'IndexLabel | None' = None,
    left_on: 'IndexLabel | None' = None,
    right_on: 'IndexLabel | None' = None,
    left_index: 'bool' = False,
    right_index: 'bool' = False,
    sort: 'bool' = False,
    suffixes: 'Suffixes' = ('_x', '_y'),
    copy: 'bool' = True,
    indicator: 'bool' = False,
    validate: 'str | None' = None,
) -> 'DataFrame'
  • left:左表
  • right:右表
  • how:关联的方式,{'left', 'right', 'outer', 'inner', 'cross'}, 默认关联方式为 'inner'
  • on:关联时指定的字段,两个表共有的
  • left_on:关联时用到左表中的字段,在两个表不共有关联字段时使用
  • right_on:关联时用到右表中的字段,在两个表不共有关联字段时使用

以上参数在实际工作中经常使用,其他参数不再做介绍

案例: 数据关联

merge 的使用与SQL中的 join 很像,使用方式基本一致,既有内连接,也有外连接,用起来基本没有什么难度

两者区别

  • concat 只是 pandas 下的方法,而 merge 即是 pandas 下的方法,又是DataFrame 下的方法
  • concat 可以横向、纵向拼接,又起到关联的作用
  • merge 只能进行关联,也就是纵向拼接
  • concat 可以同时处理多个数据框DataFrame,而 merge 只能同时处理 2 个数据框

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

在进行数据建模时(有时也叫训练模型),我们需要先经过数据清洗、特征选择与特征构造等预处理步骤,然后构造一个模型进行训练,其中One-Hot编码属于数据清洗步骤里面。

One-Hot意义

在进行特征处理时,分类数据和顺序数据这种字符型变量,无法直接用于计算,那么就需要进行数值化处理。

其中分类数据,比如一个特征包含红(R),绿(G),蓝(B)3个分类,那么怎么给这3个分类进行数值化处理呢,可以直接用1,2,3来表示吗?

肯定不行,如果用1,2,3表示,那么3种颜色之间就会产生等级差异,本来他们之间应该是平等的,这时就需要进行one-hot编码(哑变量),如下图所示的转换 one-hot

实操数据

利用西瓜数据集(部分特征)为例进行实操,这个数据在网上都可下载到 西瓜数据集 读取西瓜数据到数据框里面

import pandas as pd

data = pd.read_excel('西瓜数据集.xlsx', sheet_name='西瓜')

data.head()

读取西瓜数据

常用方法

  • pandas里面的get_dummies方法

这个方法是最简单,最直接的方法

#也可以用concat,join
data_onehot=data.merge(pd.get_dummies(data,columns=['色泽','触感']),on='编号')    

data_onehot.head()

pd.get_dummies

  • sklearn里面的One-HotEncoder方法

利用One-HotEncoder进行转换

from sklearn.preprocessing import OneHotEncoder

one_hot=OneHotEncoder()

data_temp=pd.DataFrame(one_hot.fit_transform(data[['色泽','触感']]).toarray(),
             columns=one_hot.get_feature_names(['色泽','触感']),dtype='int32')
data_onehot=pd.concat((data,data_temp),axis=1)    #也可以用merge,join

data_onehot.head()

OneHotEncoder

  • 自定义函数方法
def OneHot(df,columns):
    df_new=df.copy()
    for column in columns:
        value_sets=df_new[column].unique()
        for value_unique in value_sets:
            col_name_new=column+'_'+value_unique
            df_new[col_name_new]=(df_new[column]==value_unique)
            df_new[col_name_new]=df_new[col_name_new].astype('int32')
    return df_new

data_onehot_def=OneHot(data,columns=['色泽','触感'])

data_onehot_def.head()

OneHot_def

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

最近在进行财务平台报告自动化建设,由于涉及的都是Excel文件,并且文件里面有很多合并单元格情况,所以在处理数据时用pandas不是很方便,综合考虑后利用VBA来处理。

自己也是先学的VBA,后学的Python,所以平台上的一些功能很快开发完,接下来问题就出现了,在Python调用VBA时,出现程序不能运行的情况,经过反复排查发现是宏信任级别的问题。

宏已被禁用.png

出现这种情况,可以通过手动修改宏的信任级别,在文件---选项----信任中心----宏设置,把宏设置为“启用所有宏”,如下所示:

信任中心.png 信任级别.png

但是作为自动化平台,需要考虑到程序健壮性,不能总是手动来修改,于是在网上查找了相关资料,发现可以通过修改系统注册表的方法,来规避这个问题 注册表.png

注册表是什么?

注册表(Registry,繁体中文版Windows操作系统称之为登录档)是Microsoft Windows中的一个重要的数据库,用于存储系统和应用程序的设置信息。早在Windows 3.0推出OLE技术的时候,注册表就已经出现。随后推出的Windows NT是第一个从系统级别广泛使用注册表的操作系统。但是,从Microsoft Windows 95操作系统开始,注册表才真正成为Windows用户经常接触的内容,并在其后的操作系统中继续沿用至今。

从定义来看,注册表是用于存储系统和应用程序的设置信息

通过Python修改注册表

#导入相关库
import winreg
import win32com.client

#定义Excel程序
xl=win32com.client.Dispatch("Excel.Application")

#指定注册表文件夹位置并获取
subkey='Software\\Microsoft\\Office\\' + xl.Version+'\\Excel\\Security'
key=winreg.OpenKey(winreg.HKEY_CURRENT_USER,subkey,0, winreg.KEY_SET_VALUE)

#修改宏信任级别为1
winreg.SetValueEx(key, 'VBAWarnings', 0, winreg.REG_DWORD, 1)

通过以上程序,及可以成功把Excel宏的信任级别修改为“启用所有宏”,这样就全部实现了自动化


以上是自己实践中遇到的一些点,分享出来供大家参考学习,欢迎关注微信公众号DataShare,不定期分享干货

在网上查看了一些python处理excel库资料,参考相关内容 Python读写Excel文件第三方库汇总,你想要的都在这儿! 经过对比后,最后选择xlwings库,来处理excel文件 多库对比 由于自己会VBA,所以xlwings里面的一些函数用起来还是比较方便的,能够看懂大概用法,如果你不会VBA的话也没关系

xlwings英文版文档:http://docs.xlwings.org/en/stable/api.html

也很简单、简洁,英语很low的我也能看懂,不行就百度翻译一下,基本都可以理解怎么使用,而且这个库也一直在更新,长期来看还是比较靠谱的

#以下代码在jupyter notebook 里面运行,打印时不用print

import xlwings as xw   #引入xlwings模块


#创建excel程序对象,这里有点类似VBA,不过这里也使很多人感到困惑
#如果你理解了在excel文件里可以直接打开其他excel文件过程,你基本上就可以理解这个(菜单文件---打开)
app = xw.App(visible=True,add_book=False) 
wb = app.books.open('01.xlsx')   #指定要打开的文件

wb.sheets     #查看里面的sheet
sht1=wb.sheets['grv']    #可以直接把一个sheet赋值给一个对象,相当于引用
sht2=wb.sheets.add('python',after='grv')   #添加一个新的sheet,在sheet(grv)后面,并命名为python

sht1[0,0].value    #打印出sheet(grv)里面 A1单元格的值,这里一定要用 .value ,与vba里面的cells(1,1)有所区别,但这里相对VBA引用相对更方便,模块里面对应excel的行、列均从0开始,这与python里面其他序列下标保持一致
sht2[0,0].value=sht1[0,0].value     #可以直接引用并赋值
i=1
j=1
sht1[i,j].value         #这里也可以直接用变量,在程序里面都是用变量来引用单元格

import pandas as pd   #加载pandas模块
import numpy as np    #加载numpy模块,这里加载两个模块目的用来生成一些数据,后面数据可以直接写入excel,不用pd.to_excel

data=np.arange(0,100).reshape(20,5)
data=pd.DataFrame(data,columns=list('ABCDE'))    #创建一个数据框
sht2[1,0].value=data       #可以直接把数据框写入excel,这里的.value不能省略

wb.save()   #保存工作簿
wb.close()   #关闭工作簿
app.quit()   #退出excel程序,经测试excel程序确实退出,但任务管理器里面的进程还在运行,并没有完全退出,此处建议用下面的kill
app.kill()    #完全退出excel程序,后台没有进程在运行

注意的点:

  • 引用单元格数据时要加.value,这里和VBA的cell用法有所区别,cell默认就是.value,而这里不是
  • 退出excel时,建议用app.kill()来终止进程

以上是自己实践中遇到的一些点,分享出来供大家参考学习,欢迎关注微信公众号DataShare,不定期分享干货

背景

Lambda匿名函数在Python中经常出现,小巧切灵活,使用起来特别方便,但是小编建议大家少使用,最好多写几行代码,自定义个函数。

既然Python中存在Lambda匿名函数,那么小编本着存在即合理的原则,还是介绍一下,本篇文章翻译自《Lambda Functions in Python》,分享出来供大家参考学习

原文地址:https://www.clcoding.com/2024/03/lambda-functions-in-python.html

案例1:基本语法

常规函数

def add(x, y):
    return x + y

匿名函数

lambda_add = lambda x, y: x + y

调用2种类型函数

print(add(3, 5))   #8
print(lambda_add(3, 5))    #8

案例2:在sorted排序函数中使用匿名函数

students = [("Alice", 25), ("Bob", 30), ("Charlie", 22)]

sorted_students = sorted(students, key=lambda student: student[1])

print("Sorted Students by Age:", sorted_students)
#Sorted Students by Age: [('Charlie', 22), ('Alice', 25), ('Bob', 30)]

案例3:在filter过滤函数中使用匿名函数

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]

even_numbers = list(filter(lambda x: x % 2 == 0, numbers))

print("Even Numbers:", even_numbers)
#Even Numbers: [2, 4, 6, 8]

案例4:在map函数中使用匿名函数

numbers = [1, 2, 3, 4, 5]

squared_numbers = list(map(lambda x: x**2, numbers))

print("Squared Numbers:", squared_numbers)
#Squared Numbers: [1, 4, 9, 16, 25]

案例5:在max函数中使用匿名函数

numbers = [10, 5, 8, 20, 15]

max_number = max(numbers, key=lambda x: -x)

print("Maximum Number:", max_number)
#Maximum Number: 5

案例6:在sorted排序函数中,多个排序条件

people = [{"name": "Charlie", "age": 25}, 
          {"name": "Bob", "age": 30}, 
          {"name": "Alice", "age": 25}]

sorted_people = sorted(people, 
                       key=lambda person: (person["age"], person["name"]))

print("Sorted People:", sorted_people)
#Sorted People: [{'name': 'Alice', 'age': 25}, {'name': 'Charlie', 'age': 25}, {'name': 'Bob', 'age': 30}]

案例7:在reduce函数中使用匿名函数

from functools import reduce

numbers = [1, 2, 3, 4, 5]
product = reduce(lambda x, y: x * y, numbers)
print("Product of Numbers:", product)
#Product of Numbers: 120

案例8:在条件表达式中使用匿名函数

numbers = [10, 5, 8, 20, 15]

filtered_and_squared = list(map(lambda x: x**2 if x % 2 == 0 else x, numbers))

print("Filtered and Squared Numbers:", filtered_and_squared)
#Filtered and Squared Numbers: [100, 5, 64, 400, 15]

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

前言

Python里面在数据处理、数据分析、数据可视化、数据挖掘等领域,用到的库有Numpy、Pandas、Matplotlib、Sklearn、Scipy等,这些库都是建立在Numpy基础之上,因此学习Numpy是每个Pythoner进入数据科学领取的必经之路。

在练习Numpy时肯定离不开数据,可以随机生成,也可以自己去搜集,这些都可以,但是在手机发达的今天,还有一种数据更容易直接获得、而且还很真实,这就是照片,RGB值,下面就来详细介绍怎么把照片读取为Numpy数组。

通过读取照片获得Numpy数组

读取照片的库有很多,这里推荐大家直接用PIL(PIL即Python Imaging Library,也即为我们所称的Pillow,是一个很流行的图像库,它比opencv更为轻巧,正因如此,它深受大众的喜爱。)

以下面这张《家乡的风力发电基地.jpg》为案例,来介绍怎么把照片变为Numpy数组。 家乡的风力发电基地

  • 导入相关的库
import numpy as np
from PIL import Image
  • 读取照片
img = Image.open('家乡的风力发电基地.jpg')
img   #显示照片
  • 查看照片的属性
print(img.format)
print(img.size)  #(w,h)  宽,高
print(img.mode)

  • 转换为Numpy数组
img_array=np.array(img)
img_array

  • 查看Numpy数组属性
print(img_array.shape)   #(height,width,channel)  高,宽,通道数(RGB值)
print(img_array.dtype)   #数据类型
print(img_array.size)    #数组中所有元素的个数  400*600*3

  • 开始你的各种练习 这里简单展示一下,转为RGB三列数据

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号,不定期分享干货

背景

假如全国所有的酒店/民宿经纬度信息已知的情况下,基于当前位置,怎么快速计算附近5KM内的酒店/民宿呢?现实中有大量的这种业务场景,需要快速计算2点间的地球距离

本篇文章 不从地理的知识进行优化 ,比如当前的定位是在北京,那么没有必要去计算与上海的酒店/民宿距离;只从数据计算角度 来进行优化,看看性能大约能提升多少

小编运行环境

import sys

print('python 版本:',sys.version.split('|')[0])
#python 版本: 3.11.9

import pandas as pd

print("pandas 版本:",pd.__version__)
#pandas 版本: 2.2.2

模拟数据

本次小编用全国 10W个位置点来进行模拟

data=pd.read_excel('data_test.xlsx',
                   sheet_name='data',
                   engine='calamine')

print(len(data)) #100000

print(data.head())
#id   longitude   latitude
0  id1  114.657838  30.077734
1  id2  105.150585  27.599929
2  id3   97.396759  22.805973
3  id4  124.282532  41.283089
4  id5  115.477867  23.781731

基于单点计算距离

计算当前点与每个点之间的距离

from math import sin, asin, cos, radians, fabs, sqrt

EARTH_RADIUS = 6371      # 地球平均半径大约6371km


def hav(theta):
    s = sin(theta / 2)
    return s * s


def get_distance_hav(lat0, lng0, lat1, lng1):
    # 用haversine公式计算球面两点间的距离
    # 经纬度转换成弧度
    lat0 = radians(lat0)
    lat1 = radians(lat1)
    lng0 = radians(lng0)
    lng1 = radians(lng1)
    dlng = fabs(lng0 - lng1)
    dlat = fabs(lat0 - lat1)
    h = hav(dlat) + cos(lat0) * cos(lat1) * hav(dlng)
    distance = 2 * EARTH_RADIUS * asin(sqrt(h))      # km
    return distance

下面进行测速看看花费多长时间,在jupyter中测试的

%%timeit -n10 -r10

# import timeit

result={}
for row1 in data.itertuples():
#     start=time.time()
    
    near_id=[]
    
    for row2 in data.itertuples():
        
        if row1.id != row2.id \
        and get_distance_hav(row1.latitude,
                             row1.longitude,
                             row2.latitude,
                             row2.longitude)<=10.0:
            
            near_id.append(row2.id)
            
    result[row1.id]=near_id
    
#     print(f"耗时:{time.time()-start}秒")
    break

测速结果: 运行一次大约需要 412ms

412 ms ± 5.41 ms per loop (mean ± std. dev. of 10 runs, 10 loops each)

基于 numpy 矢量化计算距离

利用 numpy 矢量化来计算,一次计算当前点与所有点的距离,numpy 底层基于 C语言开发,可以加速运算

import numpy as np

EARTH_RADIUS = 6371  # 地球平均半径大约6371km

def hav(theta):
    s = np.sin(theta / 2)
    return s * s

def get_distance_hav_vectorized(lat0, lng0, lat1, lng1):
    # 用haversine公式计算球面两点间的距离
    # 经纬度转换成弧度
    lat0 = np.radians(lat0)
    lat1 = np.radians(lat1)
    lng0 = np.radians(lng0)
    lng1 = np.radians(lng1)
    
    dlng = np.abs(lng0 - lng1) 
    dlat = np.abs(lat0 - lat1)
    
    h = hav(dlat) + np.cos(lat0) * np.cos(lat1) * hav(dlng)
    distance = 2 * EARTH_RADIUS * np.arcsin(np.sqrt(h))  # km
    
    return distance

下面进行测速看看花费多长时间,在jupyter中测试的

%%timeit -n10 -r100

result={}

# 对 data 逐行处理
for row in data.itertuples():
#     start = time.time()
    
    t = get_distance_hav_vectorized(row.latitude,
                                    row.longitude,
                                    data['latitude'],
                                    data['longitude'])
    
    result[row.id] = ','.join(data.loc[(0<t) & (t<=10.0),'id'].to_list())
    
    break
    
# print(f"耗时:{time.time() - start}秒")

测速结果: 运行一次大约需要 7ms,相比单点计算速度提升了大约 59倍

6.72 ms ± 417 µs per loop (mean ± std. dev. of 100 runs, 10 loops each)

基于 polars 矢量化计算距离

利用 polars 矢量化来计算,polars 底层基于 Rust 语言开发,也可以加速运算

import polars as pl

print(pl.__version__) #1.2.1

data_pl = pl.read_excel('data_test.xlsx',sheet_name='data')

import math

EARTH_RADIUS = 6371  # 地球平均半径大约6371km

def hav_pl(theta):
    s = (theta / 2).sin()
    return s * s


def get_distance_hav_vectorized_pl(lat0, lng0, lat1, lng1):
    # 用haversine公式计算球面两点间的距离
    # 经纬度转换成弧度
    
    lat0 = math.radians(lat0)
    lng0 = math.radians(lng0)
    
    lat1=lat1.to_frame().select(pl.col("latitude").radians())
    lng1=lng1.to_frame().select(pl.col("longitude").radians())
    
    
    
    dlng = (lng0 - lng1.to_series()).abs()
    dlat = (lat0 - lat1.to_series()).abs()
    
    h = hav_pl(dlat) + math.cos(lat0) * lat1.to_series().cos() * hav_pl(dlng)
    distance = 2 * EARTH_RADIUS * h.sqrt().arcsin()  # km
    
    return distance

下面进行测速看看花费多长时间,在jupyter中测试的

%%timeit -n10 -r100

result={}

for row in data_pl.iter_rows(named=True):
    
    t = get_distance_hav_vectorized_pl(row['latitude'],
                                       row['longitude'],
                                       data_pl['latitude'],
                                       data_pl['longitude'])
    
    
    result[row['id']] = ','.join(data_pl.filter((t>0) & (t<=5))['id'].to_list())
    
    break

测速结果: 运行多次后,测速结果稳定在 4.1ms左右,相比 numpy 略好一些

4.07 ms ± 162 µs per loop (mean ± std. dev. of 100 runs, 10 loops each)

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

把数据导出到Excel中时,有时需要对列的顺序进行调整,按业务需求进行排列,并且字段名字不能是英文,这样方便业务人员查看与理解数据,在 pandas 中有相应的函数可以满足以上2个要求,让我们来学习一下吧

重排列:pandas.DataFrame.reindex

列重名:pandas.DataFrame.rename

小编运行环境

import sys

print('python 版本:',sys.version.split('|')[0])
#python 版本: 3.11.9

import pandas as pd

print("pandas 版本:",pd.__version__)
#pandas 版本: 2.2.2

模拟数据

data = pd.read_excel('演示数据.xlsx')

模拟数据

用 reindex 对字段顺序,重新排序

new_columns=['序号','字段3','字段1','字段2']

data_reindex=data.reindex(columns=new_columns)

reindex

用 rename 对字段重新命名

方法1

columns_mapper ={
    '序号':'index',
    '字段1':'col1',
    '字段2':'col2',
    '字段3':'col3'
}

data_rename1 = data.rename(columns=columns_mapper)

rename方法1

方法2

def fn_columns_mapper(col):
    if '字段' in col:
        new_col = col.replace('字段','col')
    else:
        new_col = 'col0'
        
    return new_col

data_rename2 = data.rename(columns=fn_columns_mapper)

rename方法2

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

在Python数据处理与分析中,大家在处理数据时,使用的基本都是 Pandas ,该库非常好用。随着 Rust 的出圈,基于其开发的 Polars 库,逐渐赢得大家的喜爱,在某些功能上更优于 Pandas。于是小编在自学的过程中,逐步整理一些资料供大家参考学习,这些资料会分享到github

仓库地址:https://github.com/DataShare-duo/polars_learn

PS:为了学习 Polars,小编先了解一遍 Rust,《Rust权威指南》

小编环境

import sys

print('python 版本:',sys.version.split('|')[0])
#python 版本: 3.11.5 

import polars as pl

print("polars 版本:",pl.__version__)
#polars 版本: 0.20.22

读取文件

polars读取文件数据的方式基本与pands一致,所以上手起来很方便,以下演示是在jupyter notebook中执行

  • 读取csv文件
data_csv=pl.read_csv('./data/iris.csv')

data_csv.shape
#(150, 6)
  • 读取 excel 文件
  1. 默认解析引擎 xlsx2csv,需要额外安装 pip install xlsx2csv
  2. 设置 engine='calamine' 时,需要额外安装 pip install fastexcel,建议用该解析引擎,速度更快
data_excel=pl.read_excel('./data/iris.xlsx',sheet_name='iris',engine='calamine')

data_excel.shape
#(150, 6)

%timeit pl.read_excel('./data/iris.xlsx',sheet_name='iris')
#13.9 ms ± 69.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit pl.read_excel('./data/iris.xlsx',sheet_name='iris',engine='calamine')  
#2.9 ms ± 69.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  • 读取 txt 文件
data_txt=pl.read_csv('./data/iris.txt',separator='\t')

data_txt.shape
#(150, 6)
  • 读取网络上的文件
url='https://raw.githubusercontent.com/DataShare-duo/Data_for_ML-Deeplearning/master/iris.csv'

data_url=pl.read_csv(url)

data_url.shape
#(150, 6)

写入文件

  • 写入csv文件
data_csv.write_csv('./data/data_write.csv')
  • 写入excel文件 默认的浮点数为3位,可以通过 float_precision 参数进行设置
data_csv.write_excel('./data/data_write.xlsx',float_precision=1)

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

polars学习系列文章,第2篇,上下文与表达式。
该系列文章会分享到github,大家可以去下载jupyter文件

仓库地址:https://github.com/DataShare-duo/polars_learn

上下文与表达式概述

官方文档表述:

Polars has developed its own Domain Specific Language (DSL) for transforming data. The language is very easy to use and allows for complex queries that remain human readable. The two core components of the language are Contexts and Expressions

机器翻译: Polars 开发了自己的特定领域语言 (DSL),用于转换数据。 该语言非常容易使用,允许进行复杂的查询,但仍保持人类可读性。 该语言的两个核心组成部分是上下文和表达式

小编加工后的翻译: Polars 自己设计了一套用于处理数据的功能。 该功能易于使用,而且能以易理解的方式进行复杂的数据处理。 上下文与表达式是该功能的两个核心组成部分。

1. Contexts 上下文 上下文是指需要计算表达式的上下文

  • 选择:df.select(...),df.with_columns(...)
  • 过滤:df.filter()
  • 分组聚合:df.group_by(...).agg(...)

2. Expressions 表达式 表达式是许多数据科学运算的核心:

  • 选取特定的列
  • 从一列中抽取特定的行
  • 将一列与值相乘
  • 从一个日期列中,提取年份
  • 将一列字符串转换为小写
  • ......

综上所述,在Polars中,Contexts 上下文 与 Expressions 表达式,需要结合使用

小编运行环境

import sys

print('python 版本:',sys.version.split('|')[0])
#python 版本: 3.11.5 

import polars as pl

print("polars 版本:",pl.__version__)
#polars 版本: 0.20.22

演示数据

df=pl.read_csv('./data/iris.csv')

print(df.head(10))
#shape: (10, 6)
┌───────┬──────────────┬─────────────┬──────────────┬─────────────┬─────────┐
│ index ┆ Sepal.Length ┆ Sepal.Width ┆ Petal.Length ┆ Petal.Width ┆ Species │
│ ---   ┆ ---          ┆ ---         ┆ ---          ┆ ---         ┆ ---     │
│ i64   ┆ f64          ┆ f64         ┆ f64          ┆ f64         ┆ str     │
╞═══════╪══════════════╪═════════════╪══════════════╪═════════════╪═════════╡
│ 1     ┆ 5.1          ┆ 3.5         ┆ 1.4          ┆ 0.2         ┆ setosa  │
│ 2     ┆ 4.9          ┆ 3.0         ┆ 1.4          ┆ 0.2         ┆ setosa  │
│ 3     ┆ 4.7          ┆ 3.2         ┆ 1.3          ┆ 0.2         ┆ setosa  │
│ 4     ┆ 4.6          ┆ 3.1         ┆ 1.5          ┆ 0.2         ┆ setosa  │
│ 5     ┆ 5.0          ┆ 3.6         ┆ 1.4          ┆ 0.2         ┆ setosa  │
│ 6     ┆ 5.4          ┆ 3.9         ┆ 1.7          ┆ 0.4         ┆ setosa  │
│ 7     ┆ 4.6          ┆ 3.4         ┆ 1.4          ┆ 0.3         ┆ setosa  │
│ 8     ┆ 5.0          ┆ 3.4         ┆ 1.5          ┆ 0.2         ┆ setosa  │
│ 9     ┆ 4.4          ┆ 2.9         ┆ 1.4          ┆ 0.2         ┆ setosa  │
│ 10    ┆ 4.9          ┆ 3.1         ┆ 1.5          ┆ 0.1         ┆ setosa  │
└───────┴──────────────┴─────────────┴──────────────┴─────────────┴─────────┘

df.shape
#(150, 6)

选取需要的列

df.select(pl.col("Sepal.Length"))  #选取特定的列

df.select(pl.col("Sepal.Length","Petal.Length"))

df.select(pl.col("*"))  #选取所有列

df.select(pl.all())  #选取所有列

df.select(pl.col("*").exclude("index", "Species"))  #选取列时,排除特定列

df.select(pl.col("^.*Length$"))  #支持正则表达式,需要以 ^ 开始 $ 结尾

df.select(pl.col(pl.Float64))  #根据列的类型,进行选取

筛选出需要的行

df.filter(pl.col("Sepal.Length")>5)  

df.filter((pl.col("Sepal.Length")>5) & (pl.col("Petal.Length")>5))  
#需要把2个条件分别括起来!!!

df.filter((pl.col("Sepal.Length")>5) | (pl.col("Petal.Length")>5))

df.select(pl.col("Sepal.Width","Petal.Width").filter(pl.col("Sepal.Length")>5))
#根据过滤条件,选取特定列

增加新列

df.with_columns(pl.lit(10),pl.lit(2).alias("lit_5"))  #增加常数列,并设置别名

df.with_columns(pl.max("Sepal.Length").alias("max_Sepal.Length"),
                pl.min("Sepal.Length").alias("min_Sepal.Length"),
                pl.mean("Sepal.Length").alias("avg_Sepal.Length"),
                pl.std("Sepal.Length").alias("std_Sepal.Length")
               )  #有点类似窗口函数

数值列运算

df.select(pl.col("Sepal.Length"),
          (pl.col("Sepal.Length")*100).alias("Sepal.Length * 100"),
          (pl.col("Sepal.Length")/100).alias("Sepal.Length / 100"),
          (pl.col("Sepal.Length")/pl.max("Sepal.Length")).alias("Sepal.Length /max_Sepal.Length")
         )

字段串列运算

df.select(pl.col("Species"),
          pl.col("Species").str.len_bytes().alias("byte_count"),
          pl.col("Species").str.len_chars().alias("chars_count")
         )

df.select(pl.col("Species"),
          pl.col("Species").str.contains("set|vir").alias("regex"),
          pl.col("Species").str.starts_with("set").alias("starts_with"),
          pl.col("Species").str.ends_with("ca").alias("ends_with"),
         )

去重统计

df.select(pl.col("Species").n_unique())

分组聚合运算

df.group_by("Species").agg(
    pl.len(),
    pl.col("index"),
    pl.count("Sepal.Length").name.suffix("_count_1"),  #别名,另一种方式
    pl.col("Sepal.Length").count().name.suffix("_count_2"),
    pl.mean("Sepal.Length").name.suffix("_mean"),
    pl.std("Sepal.Length").name.suffix("_std"),
)

df.group_by("Species").agg(
    (pl.col("Sepal.Length")>5).sum().alias("Sepal.Length>5"),
    (pl.col("Petal.Length")>5).sum().alias("Petal.Length>5"),
)

排序

df.sort("Sepal.Length",descending=True)

df.sort(["Sepal.Length","Petal.Length"],descending=[True,False])

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

polars学习系列文章,第3篇 数据类型转换。
该系列文章会分享到github,大家可以去下载jupyter文件

仓库地址:https://github.com/DataShare-duo/polars_learn

小编运行环境

import sys

print('python 版本:',sys.version.split('|')[0])
#python 版本: 3.11.5 

import polars as pl

print("polars 版本:",pl.__version__)
#polars 版本: 0.20.22

数据类型转换

数据类型转换,主要是通过 cast 方法来进行操作,该方法中有个参数 strict ,该参数决定当原数据类型不能转换为目标数据类型时,应该如何处理

  • 严格模式, strict=True(该参数默认是True),就会进行报错,打印出详细的错误信息
  • 非严格模式, strict=False ,不会报错,无法转换为目标数据类型的值都会被置为 null

pandas 中数据类型转换使用的是 astype 方法

示例

数值类型 Numerics

浮点型数值转换为整型时,会向下取整;大范围的数据类型转换为小范围数据类型时,如果数值溢出时,默认会报错,如果设置了 strict=False,则会被置为 null

df = pl.DataFrame(
    {
        "integers": [1, 2, 3, 4, 5],
        "big_integers": [1, 10000002, 3, 10000004, 10000005],
        "floats": [4.0, 5.0, 6.0, 7.0, 8.0],
        "floats_with_decimal": [4.532, 5.5, 6.5, 7.5, 8.5],
    }
)

print(df)
shape: (5, 4)
┌──────────┬──────────────┬────────┬─────────────────────┐
│ integers ┆ big_integers ┆ floats ┆ floats_with_decimal │
│ ---      ┆ ---          ┆ ---    ┆ ---                 │
│ i64      ┆ i64          ┆ f64    ┆ f64                 │
╞══════════╪══════════════╪════════╪═════════════════════╡
│ 1        ┆ 1            ┆ 4.0    ┆ 4.532               │
│ 2        ┆ 10000002     ┆ 5.0    ┆ 5.5                 │
│ 3        ┆ 3            ┆ 6.0    ┆ 6.5                 │
│ 4        ┆ 10000004     ┆ 7.0    ┆ 7.5                 │
│ 5        ┆ 10000005     ┆ 8.0    ┆ 8.5                 │
└──────────┴──────────────┴────────┴─────────────────────┘

out=df.select(
        pl.col("integers").cast(pl.Float32).alias("integers_as_floats"),
        pl.col("floats").cast(pl.Int32).alias("floats_as_integers"),
        pl.col("floats_with_decimal").cast(pl.Int32).alias("floats_with_decimal_as_integers")
    )

print(out)
shape: (5, 3)
┌────────────────────┬────────────────────┬─────────────────────────────────┐
│ integers_as_floats ┆ floats_as_integers ┆ floats_with_decimal_as_integers │
│ ---                ┆ ---                ┆ ---                             │
│ f32                ┆ i32                ┆ i32                             │
╞════════════════════╪════════════════════╪═════════════════════════════════╡
│ 1.0                ┆ 4                  ┆ 4                               │
│ 2.0                ┆ 5                  ┆ 5                               │
│ 3.0                ┆ 6                  ┆ 6                               │
│ 4.0                ┆ 7                  ┆ 7                               │
│ 5.0                ┆ 8                  ┆ 8                               │
└────────────────────┴────────────────────┴─────────────────────────────────┘

#如果不溢出的类型转换,可以节省内存
out=df.select(
        pl.col("integers").cast(pl.Int16).alias("integers_smallfootprint"),
        pl.col("floats").cast(pl.Float32).alias("floats_smallfootprint"),
    )

print(out)
shape: (5, 2)
┌─────────────────────────┬───────────────────────┐
│ integers_smallfootprint ┆ floats_smallfootprint │
│ ---                     ┆ ---                   │
│ i16                     ┆ f32                   │
╞═════════════════════════╪═══════════════════════╡
│ 1                       ┆ 4.0                   │
│ 2                       ┆ 5.0                   │
│ 3                       ┆ 6.0                   │
│ 4                       ┆ 7.0                   │
│ 5                       ┆ 8.0                   │
└─────────────────────────┴───────────────────────┘

try:
    out = df.select(pl.col("big_integers").cast(pl.Int8))
    print(out)
except Exception as e:
    print(e)
#conversion from `i64` to `i8` failed in column 'big_integers' for 3 out of 5 values: [10000002, 10000004, 10000005]

out=df.select(pl.col("big_integers").cast(pl.Int8, strict=False))
print(out)
shape: (5, 1)
┌──────────────┐
│ big_integers │
│ ---          │
│ i8           │
╞══════════════╡
│ 1            │
│ null         │
│ 3            │
│ null         │
│ null         │
└──────────────┘

字符串类型 Strings

df = pl.DataFrame(
    {
        "integers": [1, 2, 3, 4, 5],
        "float": [4.0, 5.03, 6.0, 7.0, 8.0],
        "floats_as_string": ["4.0", "5.0", "6.0", "7.0", "8.0"],
    }
)

print(df)
shape: (5, 3)
┌──────────┬───────┬──────────────────┐
│ integers ┆ float ┆ floats_as_string │
│ ---      ┆ ---   ┆ ---              │
│ i64      ┆ f64   ┆ str              │
╞══════════╪═══════╪══════════════════╡
│ 1        ┆ 4.0   ┆ 4.0              │
│ 2        ┆ 5.03  ┆ 5.0              │
│ 3        ┆ 6.0   ┆ 6.0              │
│ 4        ┆ 7.0   ┆ 7.0              │
│ 5        ┆ 8.0   ┆ 8.0              │
└──────────┴───────┴──────────────────┘

out=df.select(
        pl.col("integers").cast(pl.String),
        pl.col("float").cast(pl.String),
        pl.col("floats_as_string").cast(pl.Float64),
    )

print(out)
shape: (5, 3)
┌──────────┬───────┬──────────────────┐
│ integers ┆ float ┆ floats_as_string │
│ ---      ┆ ---   ┆ ---              │
│ str      ┆ str   ┆ f64              │
╞══════════╪═══════╪══════════════════╡
│ 1        ┆ 4.0   ┆ 4.0              │
│ 2        ┆ 5.03  ┆ 5.0              │
│ 3        ┆ 6.0   ┆ 6.0              │
│ 4        ┆ 7.0   ┆ 7.0              │
│ 5        ┆ 8.0   ┆ 8.0              │
└──────────┴───────┴──────────────────┘

df = pl.DataFrame({"strings_not_float": ["4.0", "not_a_number", "6.0", "7.0", "8.0"]})
print(df)
shape: (5, 1)
┌───────────────────┐
│ strings_not_float │
│ ---               │
│ str               │
╞═══════════════════╡
│ 4.0               │
│ not_a_number      │
│ 6.0               │
│ 7.0               │
│ 8.0               │
└───────────────────┘

#运行会报错
out=df.select(pl.col("strings_not_float").cast(pl.Float64))

#设置非严格模式,忽略错误,置为null
out=df.select(pl.col("strings_not_float").cast(pl.Float64,strict=False))
print(out)
shape: (5, 1)
┌───────────────────┐
│ strings_not_float │
│ ---               │
│ f64               │
╞═══════════════════╡
│ 4.0               │
│ null              │
│ 6.0               │
│ 7.0               │
│ 8.0               │
└───────────────────┘

布尔类型 Booleans

数值型与布尔型可以相互转换,但是不允许字符型转换为布尔型

df = pl.DataFrame(
    {
        "integers": [-1, 0, 2, 3, 4],
        "floats": [0.0, 1.0, 2.0, 3.0, 4.0],
        "bools": [True, False, True, False, True],
    }
)

print(df)
shape: (5, 3)
┌──────────┬────────┬───────┐
│ integers ┆ floats ┆ bools │
│ ---      ┆ ---    ┆ ---   │
│ i64      ┆ f64    ┆ bool  │
╞══════════╪════════╪═══════╡
│ -1       ┆ 0.0    ┆ true  │
│ 0        ┆ 1.0    ┆ false │
│ 2        ┆ 2.0    ┆ true  │
│ 3        ┆ 3.0    ┆ false │
│ 4        ┆ 4.0    ┆ true  │
└──────────┴────────┴───────┘

out=df.select(pl.col("integers").cast(pl.Boolean), 
              pl.col("floats").cast(pl.Boolean)
             )
print(out)
shape: (5, 2)
┌──────────┬────────┐
│ integers ┆ floats │
│ ---      ┆ ---    │
│ bool     ┆ bool   │
╞══════════╪════════╡
│ true     ┆ false  │
│ false    ┆ true   │
│ true     ┆ true   │
│ true     ┆ true   │
│ true     ┆ true   │
└──────────┴────────┘

时间类型 Dates

DateDatetime 等时间数据类型表示为自纪元(1970年1月1日)以来的天数(Date)和微秒数(Datetime),因此数值类型与时间数据类型能直接相互转换

字符串类型与时间类型,可以通过 dt.to_string、str.to_datetime进行相互转换

from datetime import date, datetime

df = pl.DataFrame(
    {
        "date": pl.date_range(date(2022, 1, 1), date(2022, 1, 5), eager=True),
        "datetime": pl.datetime_range(
            datetime(2022, 1, 1), datetime(2022, 1, 5), eager=True
        ),
    }
)

print(df)
shape: (5, 2)
┌────────────┬─────────────────────┐
│ date       ┆ datetime            │
│ ---        ┆ ---                 │
│ date       ┆ datetime[μs]        │
╞════════════╪═════════════════════╡
│ 2022-01-01 ┆ 2022-01-01 00:00:00 │
│ 2022-01-02 ┆ 2022-01-02 00:00:00 │
│ 2022-01-03 ┆ 2022-01-03 00:00:00 │
│ 2022-01-04 ┆ 2022-01-04 00:00:00 │
│ 2022-01-05 ┆ 2022-01-05 00:00:00 │
└────────────┴─────────────────────┘

out=df.select(pl.col("date").cast(pl.Int64),
              pl.col("datetime").cast(pl.Int64)
             )

print(out)
shape: (5, 2)
┌───────┬──────────────────┐
│ date  ┆ datetime         │
│ ---   ┆ ---              │
│ i64   ┆ i64              │
╞═══════╪══════════════════╡
│ 18993 ┆ 1640995200000000 │
│ 18994 ┆ 1641081600000000 │
│ 18995 ┆ 1641168000000000 │
│ 18996 ┆ 1641254400000000 │
│ 18997 ┆ 1641340800000000 │
└───────┴──────────────────┘

df = pl.DataFrame(
    {
        "date": pl.date_range(date(2022, 1, 1), date(2022, 1, 5), eager=True),
        "string": [
            "2022-01-01",
            "2022-01-02",
            "2022-01-03",
            "2022-01-04",
            "2022-01-05",
        ],
    }
)

print(df)
shape: (5, 2)
┌────────────┬────────────┐
│ date       ┆ string     │
│ ---        ┆ ---        │
│ date       ┆ str        │
╞════════════╪════════════╡
│ 2022-01-01 ┆ 2022-01-01 │
│ 2022-01-02 ┆ 2022-01-02 │
│ 2022-01-03 ┆ 2022-01-03 │
│ 2022-01-04 ┆ 2022-01-04 │
│ 2022-01-05 ┆ 2022-01-05 │
└────────────┴────────────┘

out=df.select(
    pl.col("date").dt.to_string("%Y-%m-%d"),
    pl.col("string").str.to_datetime("%Y-%m-%d"),
    pl.col("string").str.to_date("%Y-%m-%d").alias("string_to_data")
)

print(out)
shape: (5, 3)
┌────────────┬─────────────────────┬────────────────┐
│ date       ┆ string              ┆ string_to_data │
│ ---        ┆ ---                 ┆ ---            │
│ str        ┆ datetime[μs]        ┆ date           │
╞════════════╪═════════════════════╪════════════════╡
│ 2022-01-01 ┆ 2022-01-01 00:00:00 ┆ 2022-01-01     │
│ 2022-01-02 ┆ 2022-01-02 00:00:00 ┆ 2022-01-02     │
│ 2022-01-03 ┆ 2022-01-03 00:00:00 ┆ 2022-01-03     │
│ 2022-01-04 ┆ 2022-01-04 00:00:00 ┆ 2022-01-04     │
│ 2022-01-05 ┆ 2022-01-05 00:00:00 ┆ 2022-01-05     │
└────────────┴─────────────────────┴────────────────┘

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

polars学习系列文章,第4篇 字符串数据处理
该系列文章会分享到github,大家可以去下载jupyter文件,进行参考学习

仓库地址:https://github.com/DataShare-duo/polars_learn

小编运行环境

import sys

print('python 版本:',sys.version.split('|')[0])
#python 版本: 3.11.9

import polars as pl

print("polars 版本:",pl.__version__)
#polars 版本: 0.20.22

字符串长度

可以获取字符串中的字符数或者字节数

df = pl.DataFrame({"animal": ["Crab", "cat and dog", "rab$bit", '张',None]})

out = df.select(
    pl.col("animal").str.len_bytes().alias("byte_count"),  #字节数
    pl.col("animal").str.len_chars().alias("letter_count"),  #字符串数
)
print(out)

shape: (5, 2)
┌────────────┬──────────────┐
│ byte_count ┆ letter_count │
│ ---        ┆ ---          │
│ u32        ┆ u32          │
╞════════════╪══════════════╡
│ 4          ┆ 4            │
│ 11         ┆ 11           │
│ 7          ┆ 7            │
│ 3          ┆ 1            │
│ null       ┆ null         │
└────────────┴──────────────┘

判断是否包含特定字符串或正则字符串

  • contains:包含指定的字符串,或正则表达式字符串,返回ture/false
  • starts_with:判断是否以指定的字符串开头,返回ture/false
  • ends_with:判断是否以指定的字符串结尾,返回ture/false

如果包含了特殊的字符,但又不是正则表达式,需要设置参数literal=True,literal默认是 False,代表字符是正则表达式字符串

out = df.select(
    pl.col("animal"),
    pl.col("animal").str.contains("cat|bit").alias("regex"),
    pl.col("animal").str.contains("rab$", literal=True).alias("literal"),  #匹配$原始字符
    pl.col("animal").str.contains("rab$").alias("regex_pattern"),
    pl.col("animal").str.starts_with("rab").alias("starts_with"),
    pl.col("animal").str.ends_with("dog").alias("ends_with"),
)
print(out)

shape: (5, 6)
┌─────────────┬───────┬─────────┬───────────────┬─────────────┬───────────┐
│ animal      ┆ regex ┆ literal ┆ regex_pattern ┆ starts_with ┆ ends_with │
│ ---         ┆ ---   ┆ ---     ┆ ---           ┆ ---         ┆ ---       │
│ str         ┆ bool  ┆ bool    ┆ bool          ┆ bool        ┆ bool      │
╞═════════════╪═══════╪═════════╪═══════════════╪═════════════╪═══════════╡
│ Crab        ┆ false ┆ false   ┆ true          ┆ false       ┆ false     │
│ cat and dog ┆ true  ┆ false   ┆ false         ┆ false       ┆ true      │
│ rab$bit     ┆ true  ┆ true    ┆ false         ┆ true        ┆ false     │
│ 张          ┆ false ┆ false   ┆ false         ┆ false       ┆ false     │
│ null        ┆ null  ┆ null    ┆ null          ┆ null        ┆ null      │
└─────────────┴───────┴─────────┴───────────────┴─────────────┴───────────┘

正则表达式的各种标识,需要写到字符串开始,用括号括起来,(?iLmsuxU)

out=pl.DataFrame({"s": ["AAA", "aAa", "aaa"]}).with_columns(
    default_match=pl.col("s").str.contains("AA"),
    insensitive_match=pl.col("s").str.contains("(?i)AA")  #忽略大小写
)

print(out)

shape: (3, 3)
┌─────┬───────────────┬───────────────────┐
│ s   ┆ default_match ┆ insensitive_match │
│ --- ┆ ---           ┆ ---               │
│ str ┆ bool          ┆ bool              │
╞═════╪═══════════════╪═══════════════════╡
│ AAA ┆ true          ┆ true              │
│ aAa ┆ false         ┆ true              │
│ aaa ┆ false         ┆ true              │
└─────┴───────────────┴───────────────────┘

根据正则表达式提取特定字符

使用extract方法,根据提供的正则表达式模式,进行提取匹配到的字符串,需要提供想要获取的组索引 group_index,默认是第1个

df = pl.DataFrame(
    {
        "a": [
            "http://vote.com/ballon_dor?candidate=messi&ref=polars",
            "http://vote.com/ballon_dor?candidat=jorginho&ref=polars",
            "http://vote.com/ballon_dor?candidate=ronaldo&ref=polars",
        ]
    }
)
out = df.select(
    a1=pl.col("a").str.extract(r"candidate=(\w+)", group_index=1),
    a2=pl.col("a").str.extract(r"candidate=(\w+)", group_index=0),
    a3=pl.col("a").str.extract(r"candidate=(\w+)")  #默认获取第1个
)
print(out)

shape: (3, 3)
┌─────────┬───────────────────┬─────────┐
│ a1      ┆ a2                ┆ a3      │
│ ---     ┆ ---               ┆ ---     │
│ str     ┆ str               ┆ str     │
╞═════════╪═══════════════════╪═════════╡
│ messi   ┆ candidate=messi   ┆ messi   │
│ null    ┆ null              ┆ null    │
│ ronaldo ┆ candidate=ronaldo ┆ ronaldo │
└─────────┴───────────────────┴─────────┘

如果想获取所有正则表达式匹配到的字符串,需要使用 extract_all 方法,结果是一个列表

df = pl.DataFrame({"foo": ["123 bla 45 asd", "xyz 678 910t"]})
out = df.select(
    pl.col("foo").str.extract_all(r"(\d+)").alias("extracted_nrs"),
)
print(out)

shape: (2, 1)
┌────────────────┐
│ extracted_nrs  │
│ ---            │
│ list[str]      │
╞════════════════╡
│ ["123", "45"]  │
│ ["678", "910"] │
└────────────────┘

字符串替换

  • replace:替换第一次匹配到的字符串,为新的字符串
  • replace_all:替换所有匹配到的字符串,为新的字符串
df = pl.DataFrame({"id": [1, 2], "text": ["abc123abc", "abc456"]})
out = df.with_columns(
    s1=pl.col("text").str.replace(r"abc\b", "ABC"), #\b 字符串结束位置,以 abc 出现在字符串结尾处
    s2=pl.col("text").str.replace("a", "-"), #只替换第一次出现的 a
    s3=pl.col("text").str.replace_all("a", "-", literal=True) #替换所有的 a
)
print(out)

shape: (2, 5)
┌─────┬───────────┬───────────┬───────────┬───────────┐
│ id  ┆ text      ┆ s1        ┆ s2        ┆ s3        │
│ --- ┆ ---       ┆ ---       ┆ ---       ┆ ---       │
│ i64 ┆ str       ┆ str       ┆ str       ┆ str       │
╞═════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ 1   ┆ abc123abc ┆ abc123ABC ┆ -bc123abc ┆ -bc123-bc │
│ 2   ┆ abc456    ┆ abc456    ┆ -bc456    ┆ -bc456    │
└─────┴───────────┴───────────┴───────────┴───────────┘

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

polars学习系列文章,第5篇 包含的数据结构,与 pandas 一样,polars 包含的数据结构是:SeriesDataFrame,大部分操作与pandas 保持一致,减少了大家的学习难度

该系列文章会分享到github,大家可以去下载jupyter文件,进行参考学习

仓库地址:https://github.com/DataShare-duo/polars_learn

小编运行环境

import sys

print('python 版本:',sys.version.split('|')[0])
#python 版本: 3.11.9

import polars as pl

print("polars 版本:",pl.__version__)
#polars 版本: 0.20.22

Series 数据列

Series 是一维的数据结构,其有相同的数据类型,可以理解为数据库中的一列

import polars as pl

s = pl.Series("a", [1, 2, 3, 4, 5])
print(s)

shape: (5,)
Series: 'a' [i64]
[
	1
	2
	3
	4
	5
]

数据列的操作

print(s.min())  #1
print(s.max())  #5
print(s.mean())  #3.0
print(s.count())   #5

DataFrame 数据框

DataFrame 是一个二维的数据结构,其是由一系列的 Series 组成,可以理解为一张数据表,包含很多列

from datetime import datetime

df = pl.DataFrame(
    {
        "integer": [1, 2, 3, 4, 5],
        "date": [
            datetime(2022, 1, 1),
            datetime(2022, 1, 2),
            datetime(2022, 1, 3),
            datetime(2022, 1, 4),
            datetime(2022, 1, 5),
        ],
        "float": [4.0, 5.0, 6.0, 7.0, 8.0],
    }
)

print(df)

shape: (5, 3)
┌─────────┬─────────────────────┬───────┐
│ integer ┆ date                ┆ float │
│ ---     ┆ ---                 ┆ ---   │
│ i64     ┆ datetime[μs]        ┆ f64   │
╞═════════╪═════════════════════╪═══════╡
│ 1       ┆ 2022-01-01 00:00:00 ┆ 4.0   │
│ 2       ┆ 2022-01-02 00:00:00 ┆ 5.0   │
│ 3       ┆ 2022-01-03 00:00:00 ┆ 6.0   │
│ 4       ┆ 2022-01-04 00:00:00 ┆ 7.0   │
│ 5       ┆ 2022-01-05 00:00:00 ┆ 8.0   │
└─────────┴─────────────────────┴───────┘

默认展示前5行数据,也可以传出要展示的行数

print(df.head())
#shape: (5, 3)
┌─────────┬─────────────────────┬───────┐
│ integer ┆ date                ┆ float │
│ ---     ┆ ---                 ┆ ---   │
│ i64     ┆ datetime[μs]        ┆ f64   │
╞═════════╪═════════════════════╪═══════╡
│ 1       ┆ 2022-01-01 00:00:00 ┆ 4.0   │
│ 2       ┆ 2022-01-02 00:00:00 ┆ 5.0   │
│ 3       ┆ 2022-01-03 00:00:00 ┆ 6.0   │
│ 4       ┆ 2022-01-04 00:00:00 ┆ 7.0   │
│ 5       ┆ 2022-01-05 00:00:00 ┆ 8.0   │
└─────────┴─────────────────────┴───────┘

print(df.head(3))
#shape: (3, 3)
┌─────────┬─────────────────────┬───────┐
│ integer ┆ date                ┆ float │
│ ---     ┆ ---                 ┆ ---   │
│ i64     ┆ datetime[μs]        ┆ f64   │
╞═════════╪═════════════════════╪═══════╡
│ 1       ┆ 2022-01-01 00:00:00 ┆ 4.0   │
│ 2       ┆ 2022-01-02 00:00:00 ┆ 5.0   │
│ 3       ┆ 2022-01-03 00:00:00 ┆ 6.0   │
└─────────┴─────────────────────┴───────┘

Tail

默认展示最后5行数据,也可以传出要展示的行数

print(df.tail())
#shape: (5, 3)
┌─────────┬─────────────────────┬───────┐
│ integer ┆ date                ┆ float │
│ ---     ┆ ---                 ┆ ---   │
│ i64     ┆ datetime[μs]        ┆ f64   │
╞═════════╪═════════════════════╪═══════╡
│ 1       ┆ 2022-01-01 00:00:00 ┆ 4.0   │
│ 2       ┆ 2022-01-02 00:00:00 ┆ 5.0   │
│ 3       ┆ 2022-01-03 00:00:00 ┆ 6.0   │
│ 4       ┆ 2022-01-04 00:00:00 ┆ 7.0   │
│ 5       ┆ 2022-01-05 00:00:00 ┆ 8.0   │
└─────────┴─────────────────────┴───────┘

print(df.tail(3))
#shape: (3, 3)
┌─────────┬─────────────────────┬───────┐
│ integer ┆ date                ┆ float │
│ ---     ┆ ---                 ┆ ---   │
│ i64     ┆ datetime[μs]        ┆ f64   │
╞═════════╪═════════════════════╪═══════╡
│ 3       ┆ 2022-01-03 00:00:00 ┆ 6.0   │
│ 4       ┆ 2022-01-04 00:00:00 ┆ 7.0   │
│ 5       ┆ 2022-01-05 00:00:00 ┆ 8.0   │
└─────────┴─────────────────────┴───────┘

Sample 随机抽样

print(df.sample(3))
#shape: (3, 3)
┌─────────┬─────────────────────┬───────┐
│ integer ┆ date                ┆ float │
│ ---     ┆ ---                 ┆ ---   │
│ i64     ┆ datetime[μs]        ┆ f64   │
╞═════════╪═════════════════════╪═══════╡
│ 5       ┆ 2022-01-05 00:00:00 ┆ 8.0   │
│ 3       ┆ 2022-01-03 00:00:00 ┆ 6.0   │
│ 1       ┆ 2022-01-01 00:00:00 ┆ 4.0   │
└─────────┴─────────────────────┴───────┘

Describe 数据概况

print(df.describe())
#shape: (9, 4)
┌────────────┬──────────┬─────────────────────┬──────────┐
│ statistic  ┆ integer  ┆ date                ┆ float    │
│ ---        ┆ ---      ┆ ---                 ┆ ---      │
│ str        ┆ f64      ┆ str                 ┆ f64      │
╞════════════╪══════════╪═════════════════════╪══════════╡
│ count      ┆ 5.0      ┆ 5                   ┆ 5.0      │
│ null_count ┆ 0.0      ┆ 0                   ┆ 0.0      │
│ mean       ┆ 3.0      ┆ 2022-01-03 00:00:00 ┆ 6.0      │
│ std        ┆ 1.581139 ┆ null                ┆ 1.581139 │
│ min        ┆ 1.0      ┆ 2022-01-01 00:00:00 ┆ 4.0      │
│ 25%        ┆ 2.0      ┆ 2022-01-02 00:00:00 ┆ 5.0      │
│ 50%        ┆ 3.0      ┆ 2022-01-03 00:00:00 ┆ 6.0      │
│ 75%        ┆ 4.0      ┆ 2022-01-04 00:00:00 ┆ 7.0      │
│ max        ┆ 5.0      ┆ 2022-01-05 00:00:00 ┆ 8.0      │
└────────────┴──────────┴─────────────────────┴──────────┘

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

polars学习系列文章,第6篇 Lazy / Eager API
Lazy: 延迟、惰性
Eager: 即时、实时

该系列文章会分享到github,大家可以去下载jupyter文件,进行参考学习

仓库地址:https://github.com/DataShare-duo/polars_learn

小编运行环境

import sys

print('python 版本:',sys.version.split('|')[0])
#python 版本: 3.11.9

import polars as pl

print("polars 版本:",pl.__version__)
#polars 版本: 0.20.22

Lazy / Eager API 区别

  • Eager API(即时、实时) 实时进行计算,每一步操作都会进行计算,类似pandas那样,每操作一步都会进行计算,得到这一步的结果,所见即所得,如果没有明确指定或者调用特定的方法之外,polars 基本都是使用该模式

  • Lazy API(延迟、惰性) 推迟进行计算,把所有的操作步骤先记下来,Query plan(查询计划),等到需要结果时,才统一进行计算,polars 会对这些计算步骤自动进行优化,提升性能

    • pl.scan_csvpl.scan_ 函数
    • 调用DataFrame 的 .lazy 方法,转换为 Lazy 模式

Eager API 数据处理案例

df = pl.read_csv("./data/iris.csv")
df_small = df.filter(pl.col("Sepal.Length") > 5)
df_agg = df_small.group_by("Species").agg(pl.col("Sepal.Width").mean())
print(df_agg)

#shape: (3, 2)
┌────────────┬─────────────┐
│ Species    ┆ Sepal.Width │
│ ---        ┆ ---         │
│ str        ┆ f64         │
╞════════════╪═════════════╡
│ versicolor ┆ 2.804255    │
│ virginica  ┆ 2.983673    │
│ setosa     ┆ 3.713636    │
└────────────┴─────────────┘

Lazy API 数据处理案例

q = (
    pl.scan_csv("./data/iris.csv")
    .filter(pl.col("Sepal.Length") > 5)
    .group_by("Species")
    .agg(pl.col("Sepal.Width").mean())
)

df = q.collect()
print(df)

#shape: (3, 2)
┌────────────┬─────────────┐
│ Species    ┆ Sepal.Width │
│ ---        ┆ ---         │
│ str        ┆ f64         │
╞════════════╪═════════════╡
│ virginica  ┆ 2.983673    │
│ versicolor ┆ 2.804255    │
│ setosa     ┆ 3.713636    │
└────────────┴─────────────┘

在数据处理中会对Sepal.Length进行过滤,polars 在把数据加载进内存时,只会加载符合条件的数据行,同时计算时只用到了 SpeciesSepal.Width 2列,polars 只会加载这2 列到内存,进行计算

这样的话会显著降低内存和CPU的负载,从而能够在内存中容纳更大的数据集并加快处理速度

使用建议

  • 如果你是在进行探索性分析,想知道中间的每个步骤数据情况,那么可以使用 Eager 模式
  • 如果想得到最终的计算结果,那么可以使用 Lazy 模式,让polars对中间的计算进行优化,提升数据处理效率

注:在大部分情况下,Eager API 背后其实调用的是 Lazy API,Eager 模式其实也是有查询优化

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

polars学习系列文章,第7篇 缺失值

该系列文章会分享到github,大家可以去下载jupyter文件,进行参考学习

仓库地址:https://github.com/DataShare-duo/polars_learn

小编运行环境

import sys

print('python 版本:',sys.version.split('|')[0])
#python 版本: 3.11.9

import polars as pl

print("polars 版本:",pl.__version__)
#polars 版本: 0.20.22

polars 中缺失值的定义

在 polars 中缺失值用 null 来表示,只有这1种表示方式,这个与 pandas 不同,在 pandas 中 NaN(NotaNumber)也代表是缺失值,但在polars中把 NaN 归属为一种浮点数据

df = pl.DataFrame(
    {
        "value": [1,2,3, None,5,6,None,8,9],
    },
)
print(df)
#shape: (9, 1)
┌───────┐
│ value │
│ ---   │
│ i64   │
╞═══════╡
│ 1     │
│ 2     │
│ 3     │
│ null  │
│ 5     │
│ 6     │
│ null  │
│ 8     │
│ 9     │
└───────┘

polars中缺失值包括的2种元信息

  • 缺失值数量,可以通过 null_count 方法来快速获取,因为已经是计算好的,所以调用该方法会立即返回结果
  • 有效位图(validity bitmap),代表是否是缺失值,在内存中用 0 或 1 进行编码来表示,所占的内存空间非常小,通常占用空间为(数据框长度 / 8) bytes,通过 is_null 方法来查看数据是否是缺失值
null_count_df = df.null_count()
print(null_count_df)
#shape: (1, 1)
┌───────┐
│ value │
│ ---   │
│ u32   │
╞═══════╡
│ 2     │
└───────┘


is_null_series = df.select(
    pl.col("value").is_null(),
)
print(is_null_series)
#shape: (9, 1)
┌───────┐
│ value │
│ ---   │
│ bool  │
╞═══════╡
│ false │
│ false │
│ false │
│ true  │
│ false │
│ false │
│ true  │
│ false │
│ false │
└───────┘

缺失值填充

缺失值填充主要通过 fill_null方法来处理,但是需求指定填充缺失值的方法

  • 常量,比如用 0 来填充
  • 填充策略,例如:向前、向后 等
  • 通过表达式,比如利用其他列来填充
  • 插值法
df = pl.DataFrame(
    {
        "col1": [1, 2, 3],
        "col2": [1, None, 3],
    },
)
print(df)
#shape: (3, 2)
┌──────┬──────┐
│ col1 ┆ col2 │
│ ---  ┆ ---  │
│ i64  ┆ i64  │
╞══════╪══════╡
│ 1    ┆ 1    │
│ 2    ┆ null │
│ 3    ┆ 3    │
└──────┴──────┘

常量填充

fill_literal_df = df.with_columns(
    fill=pl.col("col2").fill_null(pl.lit(2)),
)
print(fill_literal_df)
#shape: (3, 3)
┌──────┬──────┬──────┐
│ col1 ┆ col2 ┆ fill │
│ ---  ┆ ---  ┆ ---  │
│ i64  ┆ i64  ┆ i64  │
╞══════╪══════╪══════╡
│ 1    ┆ 1    ┆ 1    │
│ 2    ┆ null ┆ 2    │
│ 3    ┆ 3    ┆ 3    │
└──────┴──────┴──────┘

填充策略

填充策略:{'forward', 'backward', 'min', 'max', 'mean', 'zero', 'one'}

fill_df = df.with_columns(
    forward=pl.col("col2").fill_null(strategy="forward"),
    backward=pl.col("col2").fill_null(strategy="backward"),
)
print(fill_df)
#shape: (3, 4)
┌──────┬──────┬─────────┬──────────┐
│ col1 ┆ col2 ┆ forward ┆ backward │
│ ---  ┆ ---  ┆ ---     ┆ ---      │
│ i64  ┆ i64  ┆ i64     ┆ i64      │
╞══════╪══════╪═════════╪══════════╡
│ 1    ┆ 1    ┆ 1       ┆ 1        │
│ 2    ┆ null ┆ 1       ┆ 3        │
│ 3    ┆ 3    ┆ 3       ┆ 3        │
└──────┴──────┴─────────┴──────────┘

通过表达式

fill_median_df = df.with_columns(
    fill=pl.col("col2").fill_null(pl.median("col2")), #类型会转换为浮点型
)
print(fill_median_df)
#shape: (3, 3)
┌──────┬──────┬──────┐
│ col1 ┆ col2 ┆ fill │
│ ---  ┆ ---  ┆ ---  │
│ i64  ┆ i64  ┆ f64  │
╞══════╪══════╪══════╡
│ 1    ┆ 1    ┆ 1.0  │
│ 2    ┆ null ┆ 2.0  │
│ 3    ┆ 3    ┆ 3.0  │
└──────┴──────┴──────┘

通过插值法

fill_interpolation_df = df.with_columns(
    fill=pl.col("col2").interpolate(),  
)
print(fill_interpolation_df)
#shape: (3, 3)
┌──────┬──────┬──────┐
│ col1 ┆ col2 ┆ fill │
│ ---  ┆ ---  ┆ ---  │
│ i64  ┆ i64  ┆ f64  │
╞══════╪══════╪══════╡
│ 1    ┆ 1    ┆ 1.0  │
│ 2    ┆ null ┆ 2.0  │
│ 3    ┆ 3    ┆ 3.0  │
└──────┴──────┴──────┘

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

polars学习系列文章,第8篇 分类数据处理(Categorical data)

该系列文章会分享到github,大家可以去下载jupyter文件,进行参考学习

仓库地址:https://github.com/DataShare-duo/polars_learn

小编运行环境

import sys

print('python 版本:',sys.version.split('|')[0])
#python 版本: 3.11.9

import polars as pl

print("polars 版本:",pl.__version__)
#polars 版本: 0.20.22

分类数据 Categorical data

分类数据就是平时在数据库中能进行编码的数据,比如:性别、年龄、国家、城市、职业 等等,可以对这些数据进行编码,可以节省存储空间

Polars 支持两种不同的数据类型来处理分类数据:EnumCategorical

  • 当类别预先已知时使用 Enum,需要提前提供所有类别
  • 当不知道类别或类别不固定时,可以使用 Categorical
enum_dtype = pl.Enum(["Polar", "Panda", "Brown"])
enum_series = pl.Series(
    ["Polar", "Panda", "Brown", "Brown", "Polar"], 
    dtype=enum_dtype)

cat_series = pl.Series(
    ["Polar", "Panda", "Brown", "Brown", "Polar"], 
    dtype=pl.Categorical
)

Categorical 类型

Categorical 相对比较灵活,不用提前获取所有的类别,当有新类别时,会自动进行编码

当对来自2个不同的 Categorical 类别列直接进行拼接时,以下这种方式会比较慢,polars 是根据字符串出现的先后顺序进行编码,不同的字符串在不同的序列里面编码可能不一样,直接合并的话全局会再进行一次编码,速度会比较慢:

cat_series = pl.Series(
    ["Polar", "Panda", "Brown", "Brown", "Polar"], dtype=pl.Categorical
)
cat2_series = pl.Series(
    ["Panda", "Brown", "Brown", "Polar", "Polar"], dtype=pl.Categorical
)

#CategoricalRemappingWarning: Local categoricals have different encodings, 
#expensive re-encoding is done to perform this merge operation. 
#Consider using a StringCache or an Enum type if the categories are known in advance
print(cat_series.append(cat2_series))

可以通过使用 polars 提供的全局字符缓存 StringCache,来提升数据处理效率

with pl.StringCache():
    cat_series = pl.Series(
        ["Polar", "Panda", "Brown", "Brown", "Polar"], dtype=pl.Categorical
    )
    cat2_series = pl.Series(
        ["Panda", "Brown", "Brown", "Polar", "Polar"], dtype=pl.Categorical
    )
    print(cat_series.append(cat2_series))

Enum

上面来自2个不同类型列进行拼接的耗时的情况,在Enum中不会存在,因为已经提前获取到了全部的类别

dtype = pl.Enum(["Polar", "Panda", "Brown"])
cat_series = pl.Series(["Polar", "Panda", "Brown", "Brown", "Polar"], dtype=dtype)
cat2_series = pl.Series(["Panda", "Brown", "Brown", "Polar", "Polar"], dtype=dtype)

print(cat_series.append(cat2_series))
#shape: (10,)
#Series: '' [enum]
[
	"Polar"
	"Panda"
	"Brown"
	"Brown"
	"Polar"
	"Panda"
	"Brown"
	"Brown"
	"Polar"
	"Polar"
]

如果有编码的字符串类别,当不在提前获取的Enum中时,则会报错:OutOfBounds

dtype = pl.Enum(["Polar", "Panda", "Brown"])
try:
    cat_series = pl.Series(["Polar", "Panda", "Brown", "Black"], dtype=dtype)
except Exception as e:
    print(e)
#conversion from `str` to `enum` failed 
#in column '' for 1 out of 4 values: ["Black"]
#Ensure that all values in the input column are present 
#in the categories of the enum datatype.

比较

  • Categorical vs Categorical
  • Categorical vs String
  • Enum vs Enum
  • Enum vs String(该字符串必须要在提前获取的Enum中)

Categorical vs Categorical

with pl.StringCache():
    cat_series = pl.Series(["Brown", "Panda", "Polar"], dtype=pl.Categorical)
    cat_series2 = pl.Series(["Polar", "Panda", "Black"], dtype=pl.Categorical)
    print(cat_series == cat_series2)
#shape: (3,)
#Series: '' [bool]
[
	false
	true
	false
]

Categorical vs String

cat_series = pl.Series(["Brown", "Panda", "Polar"], dtype=pl.Categorical)
print(cat_series <= "Cat")
#shape: (3,)
#Series: '' [bool]
[
	true
	false
	false
]

cat_series = pl.Series(["Brown", "Panda", "Polar"], dtype=pl.Categorical)
cat_series_utf = pl.Series(["Panda", "Panda", "A Polar"])
print(cat_series <= cat_series_utf)
#shape: (3,)
#Series: '' [bool]
[
	true
	true
	false
]

Enum vs Enum

dtype = pl.Enum(["Polar", "Panda", "Brown"])
cat_series = pl.Series(["Brown", "Panda", "Polar"], dtype=dtype)
cat_series2 = pl.Series(["Polar", "Panda", "Brown"], dtype=dtype)
print(cat_series == cat_series2)
#shape: (3,)
#Series: '' [bool]
[
	false
	true
	false
]

Enum vs String(该字符串必须要在提前获取的Enum中)

try:
    cat_series = pl.Series(
        ["Low", "Medium", "High"], dtype=pl.Enum(["Low", "Medium", "High"])
    )
    cat_series <= "Excellent"
except Exception as e:
    print(e)
#conversion from `str` to `enum` failed 
#in column '' for 1 out of 1 values: ["Excellent"]
#Ensure that all values in the input column are present 
#in the categories of the enum datatype.

dtype = pl.Enum(["Low", "Medium", "High"])
cat_series = pl.Series(["Low", "Medium", "High"], dtype=dtype)
print(cat_series <= "Medium")
#shape: (3,)
#Series: '' [bool]
[
	true
	true
	false
]

dtype = pl.Enum(["Low", "Medium", "High"])
cat_series = pl.Series(["Low", "Medium", "High"], dtype=dtype)
cat_series2 = pl.Series(["High", "High", "Low"])
print(cat_series <= cat_series2)
#shape: (3,)
#Series: '' [bool]
[
	true
	true
	false
]

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

polars学习系列文章,第9篇 数据框关联与拼接(Join 、Concat)

该系列文章会分享到github,大家可以去下载jupyter文件,进行参考学习

仓库地址:https://github.com/DataShare-duo/polars_learn

小编运行环境

import sys

print('python 版本:',sys.version.split('|')[0])
#python 版本: 3.11.9

import polars as pl

print("polars 版本:",pl.__version__)
#polars 版本: 1.2.1

数据框关联 Join

polars 通过指定参数 how,支持以下方式的关联:

  • inner:类似sql中的 inner join,取2个数据框共同的部分
  • left:类似sql中的 left join,取左边数据框所有数据,匹配右边数据框数据,能匹配到的进行匹配,匹配不到的用 null 填充
  • full:类似sql中的 full outer join,返回2个数据框的全量数据,匹配不到的用 null 填充
  • cross:2个数据框的笛卡尔积,数据行数为,len(A) × len(B)
  • semi:用的相对比较少,左边数据框中关联字段同时存在右边数据框中,只返回左边数据框的行,有点类似 inner join,但是不全完一样,即使右边数据框有多行的,左边返回的还是单行,也就是遇到关联字段存在于右边数据框,就返回
  • anti:用的相对比较少,返回左边数据框中关联字段不存在右边数据框中的行,与 semi 相反

数据准备

df_customers = pl.DataFrame(
    {
        "customer_id": [1, 2, 3],
        "name": ["Alice", "Bob", "Charlie"],
    }
)

print(df_customers)
#shape: (3, 2)
┌─────────────┬─────────┐
│ customer_id ┆ name    │
│ ---         ┆ ---     │
│ i64         ┆ str     │
╞═════════════╪═════════╡
│ 1           ┆ Alice   │
│ 2           ┆ Bob     │
│ 3           ┆ Charlie │
└─────────────┴─────────┘

df_orders = pl.DataFrame(
    {
        "order_id": ["a", "b", "c"],
        "customer_id": [1, 2, 2],
        "amount": [100, 200, 300],
    }
)

print(df_orders)
#shape: (3, 3)
┌──────────┬─────────────┬────────┐
│ order_id ┆ customer_id ┆ amount │
│ ---      ┆ ---         ┆ ---    │
│ str      ┆ i64         ┆ i64    │
╞══════════╪═════════════╪════════╡
│ a        ┆ 1           ┆ 100    │
│ b        ┆ 2           ┆ 200    │
│ c        ┆ 2           ┆ 300    │
└──────────┴─────────────┴────────┘

Inner join

df_inner_customer_join = df_customers.join(df_orders, 
                                           on="customer_id", 
                                           how="inner")

print(df_inner_customer_join)
#shape: (3, 4)
┌─────────────┬───────┬──────────┬────────┐
│ customer_id ┆ name  ┆ order_id ┆ amount │
│ ---         ┆ ---   ┆ ---      ┆ ---    │
│ i64         ┆ str   ┆ str      ┆ i64    │
╞═════════════╪═══════╪══════════╪════════╡
│ 1           ┆ Alice ┆ a        ┆ 100    │
│ 2           ┆ Bob   ┆ b        ┆ 200    │
│ 2           ┆ Bob   ┆ c        ┆ 300    │
└─────────────┴───────┴──────────┴────────┘

Left join

df_left_join = df_customers.join(df_orders, 
                                 on="customer_id", 
                                 how="left")

print(df_left_join)
#shape: (4, 4)
┌─────────────┬─────────┬──────────┬────────┐
│ customer_id ┆ name    ┆ order_id ┆ amount │
│ ---         ┆ ---     ┆ ---      ┆ ---    │
│ i64         ┆ str     ┆ str      ┆ i64    │
╞═════════════╪═════════╪══════════╪════════╡
│ 1           ┆ Alice   ┆ a        ┆ 100    │
│ 2           ┆ Bob     ┆ b        ┆ 200    │
│ 2           ┆ Bob     ┆ c        ┆ 300    │
│ 3           ┆ Charlie ┆ null     ┆ null   │
└─────────────┴─────────┴──────────┴────────┘

Outer join

df_outer_join = df_customers.join(df_orders, 
                                  on="customer_id", 
                                  how="full")

print(df_outer_join)
#shape: (4, 5)
┌─────────────┬─────────┬──────────┬───────────────────┬────────┐
│ customer_id ┆ name    ┆ order_id ┆ customer_id_right ┆ amount │
│ ---         ┆ ---     ┆ ---      ┆ ---               ┆ ---    │
│ i64         ┆ str     ┆ str      ┆ i64               ┆ i64    │
╞═════════════╪═════════╪══════════╪═══════════════════╪════════╡
│ 1           ┆ Alice   ┆ a        ┆ 1                 ┆ 100    │
│ 2           ┆ Bob     ┆ b        ┆ 2                 ┆ 200    │
│ 2           ┆ Bob     ┆ c        ┆ 2                 ┆ 300    │
│ 3           ┆ Charlie ┆ null     ┆ null              ┆ null   │
└─────────────┴─────────┴──────────┴───────────────────┴────────┘

Cross join

df_colors = pl.DataFrame(
    {
        "color": ["red", "blue", "green"],
    }
)
print(df_colors)
#shape: (3, 1)
┌───────┐
│ color │
│ ---   │
│ str   │
╞═══════╡
│ red   │
│ blue  │
│ green │
└───────┘

df_sizes = pl.DataFrame(
    {
        "size": ["S", "M", "L"],
    }
)
#print(df_sizes)

df_cross_join = df_colors.join(df_sizes, 
                               how="cross")

print(df_cross_join)
#shape: (9, 2)
┌───────┬──────┐
│ color ┆ size │
│ ---   ┆ ---  │
│ str   ┆ str  │
╞═══════╪══════╡
│ red   ┆ S    │
│ red   ┆ M    │
│ red   ┆ L    │
│ blue  ┆ S    │
│ blue  ┆ M    │
│ blue  ┆ L    │
│ green ┆ S    │
│ green ┆ M    │
│ green ┆ L    │
└───────┴──────┘

Semi join

df_cars = pl.DataFrame(
    {
        "id": ["a", "b", "c"],
        "make": ["ford", "toyota", "bmw"],
    }
)
print(df_cars)
shape: (3, 2)
┌─────┬────────┐
│ id  ┆ make   │
│ --- ┆ ---    │
│ str ┆ str    │
╞═════╪════════╡
│ a   ┆ ford   │
│ b   ┆ toyota │
│ c   ┆ bmw    │
└─────┴────────┘

df_repairs = pl.DataFrame(
    {
        "id": ["c", "c"],
        "cost": [100, 200],
    }
)
print(df_repairs)
#shape: (2, 2)
┌─────┬──────┐
│ id  ┆ cost │
│ --- ┆ ---  │
│ str ┆ i64  │
╞═════╪══════╡
│ c   ┆ 100  │
│ c   ┆ 200  │
└─────┴──────┘

df_semi_join = df_cars.join(df_repairs, 
                            on="id", 
                            how="semi")
print(df_semi_join)
#shape: (1, 2)
┌─────┬──────┐
│ id  ┆ make │
│ --- ┆ ---  │
│ str ┆ str  │
╞═════╪══════╡
│ c   ┆ bmw  │
└─────┴──────┘

Anti join

df_anti_join = df_cars.join(df_repairs, 
                            on="id", 
                            how="anti")

print(df_anti_join)
#shape: (2, 2)
┌─────┬────────┐
│ id  ┆ make   │
│ --- ┆ ---    │
│ str ┆ str    │
╞═════╪════════╡
│ a   ┆ ford   │
│ b   ┆ toyota │
└─────┴────────┘

数据框拼接 Concat

有以下3种方式的数据框拼接:

  • 纵向拼接/垂直拼接:2个数据框有相同的字段,拼接后产生更长的数据框
  • 横向拼接/水平拼接:2个数据框没有重叠的字段,拼接后产生更宽的数据框
  • 对角拼接:2个数据框有不同的行与列,既有重叠的字段,也有非重叠的字段,拼接后产生即长又宽的数据框

纵向拼接/垂直拼接 Vertical concatenation

当没有相同的列字段时,纵向拼接会失败

df_v1 = pl.DataFrame(
    {
        "a": [1],
        "b": [3],
    }
)
df_v2 = pl.DataFrame(
    {
        "a": [2],
        "b": [4],
    }
)
df_vertical_concat = pl.concat(
    [
        df_v1,
        df_v2,
    ],
    how="vertical",
)
print(df_vertical_concat)
#shape: (2, 2)
┌─────┬─────┐
│ a   ┆ b   │
│ --- ┆ --- │
│ i64 ┆ i64 │
╞═════╪═════╡
│ 1   ┆ 3   │
│ 2   ┆ 4   │
└─────┴─────┘

横向拼接/水平拼接 Horizontal concatenation

当2个数据框有不同的行数时,拼接后短的行会用 null 进行填充

df_h1 = pl.DataFrame(
    {
        "l1": [1, 2],
        "l2": [3, 4],
    }
)
df_h2 = pl.DataFrame(
    {
        "r1": [5, 6],
        "r2": [7, 8],
        "r3": [9, 10],
    }
)
df_horizontal_concat = pl.concat(
    [
        df_h1,
        df_h2,
    ],
    how="horizontal",
)
print(df_horizontal_concat)
#shape: (2, 5)
┌─────┬─────┬─────┬─────┬─────┐
│ l1  ┆ l2  ┆ r1  ┆ r2  ┆ r3  │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 │
╞═════╪═════╪═════╪═════╪═════╡
│ 1   ┆ 3   ┆ 5   ┆ 7   ┆ 9   │
│ 2   ┆ 4   ┆ 6   ┆ 8   ┆ 10  │
└─────┴─────┴─────┴─────┴─────┘

对角拼接 Diagonal concatenation

df_d1 = pl.DataFrame(
    {
        "a": [1],
        "b": [3],
    }
)
df_d2 = pl.DataFrame(
    {
        "a": [2],
        "d": [4],
    }
)

df_diagonal_concat = pl.concat(
    [
        df_d1,
        df_d2,
    ],
    how="diagonal",
)
print(df_diagonal_concat)
#shape: (2, 3)
┌─────┬──────┬──────┐
│ a   ┆ b    ┆ d    │
│ --- ┆ ---  ┆ ---  │
│ i64 ┆ i64  ┆ i64  │
╞═════╪══════╪══════╡
│ 1   ┆ 3    ┆ null │
│ 2   ┆ null ┆ 4    │
└─────┴──────┴──────┘

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

polars学习系列文章,第10篇 时间序列类型(Time series)

该系列文章会分享到github,大家可以去下载jupyter文件,进行参考学习 仓库地址:https://github.com/DataShare-duo/polars_learn

小编运行环境

import sys

print('python 版本:',sys.version.split('|')[0])
#python 版本: 3.11.9

import polars as pl

print("polars 版本:",pl.__version__)
#polars 版本: 1.2.1

日期时间类型

Polars 原生支持解析时间序列数据,而且能执行一些复杂的操作

包含的日期时间类型:

  • Date:日期类型,例如:2014-07-08,内部表示为自1970-01-01的天数,用32位有符号整数表示
  • Datetime:日期时间类型,例如:2014-07-08 07:00:00
  • Duration:时间间隔类型
  • Time:时间类型

从文件加载数据时,解析时间

从csv文件加载数据时,可以指定 try_parse_dates=True,让polars去尝试解析日期时间

df = pl.read_csv("./data/apple_stock.csv", try_parse_dates=True)
print(df)
#shape: (100, 2)
┌────────────┬────────┐
│ Date       ┆ Close  │
│ ---        ┆ ---    │
│ date       ┆ f64    │
╞════════════╪════════╡
│ 1981-02-23 ┆ 24.62  │
│ 1981-05-06 ┆ 27.38  │
│ 1981-05-18 ┆ 28.0   │
│ 1981-09-25 ┆ 14.25  │
│ 1982-07-08 ┆ 11.0   │
│ …          ┆ …      │
│ 2012-05-16 ┆ 546.08 │
│ 2012-12-04 ┆ 575.85 │
│ 2013-07-05 ┆ 417.42 │
│ 2013-11-07 ┆ 512.49 │
│ 2014-02-25 ┆ 522.06 │
└────────────┴────────┘

字符串转换为日期时间类型

通过调用字符串的 str.to_date 方法,需要指定日期时间解析时的格式

日期时间解析格式,可参考该文档:
https://docs.rs/chrono/latest/chrono/format/strftime/index.html

df = pl.read_csv("./data/apple_stock.csv", try_parse_dates=False)

df = df.with_columns(pl.col("Date").str.to_date("%Y-%m-%d"))
print(df)
shape: (100, 2)
┌────────────┬────────┐
│ Date       ┆ Close  │
│ ---        ┆ ---    │
│ date       ┆ f64    │
╞════════════╪════════╡
│ 1981-02-23 ┆ 24.62  │
│ 1981-05-06 ┆ 27.38  │
│ 1981-05-18 ┆ 28.0   │
│ 1981-09-25 ┆ 14.25  │
│ 1982-07-08 ┆ 11.0   │
│ …          ┆ …      │
│ 2012-05-16 ┆ 546.08 │
│ 2012-12-04 ┆ 575.85 │
│ 2013-07-05 ┆ 417.42 │
│ 2013-11-07 ┆ 512.49 │
│ 2014-02-25 ┆ 522.06 │
└────────────┴────────┘

从日期时间类型中提取特定的日期类型

比如从日期时间类型列中提取年份、日期等,通过 .dt 来提取

df_with_year = df.with_columns(pl.col("Date").dt.year().alias("year"))
print(df_with_year)
#shape: (100, 3)
┌────────────┬────────┬──────┐
│ Date       ┆ Close  ┆ year │
│ ---        ┆ ---    ┆ ---  │
│ date       ┆ f64    ┆ i32  │
╞════════════╪════════╪══════╡
│ 1981-02-23 ┆ 24.62  ┆ 1981 │
│ 1981-05-06 ┆ 27.38  ┆ 1981 │
│ 1981-05-18 ┆ 28.0   ┆ 1981 │
│ 1981-09-25 ┆ 14.25  ┆ 1981 │
│ 1982-07-08 ┆ 11.0   ┆ 1982 │
│ …          ┆ …      ┆ …    │
│ 2012-05-16 ┆ 546.08 ┆ 2012 │
│ 2012-12-04 ┆ 575.85 ┆ 2012 │
│ 2013-07-05 ┆ 417.42 ┆ 2013 │
│ 2013-11-07 ┆ 512.49 ┆ 2013 │
│ 2014-02-25 ┆ 522.06 ┆ 2014 │
└────────────┴────────┴──────┘

混合时差

当有混合时差(例如,由于跨越夏令时),可以使用 dt.convert_time_zone 来进行转换

data = [
    "2021-03-27T00:00:00+0100",
    "2021-03-28T00:00:00+0100",
    "2021-03-29T00:00:00+0200",
    "2021-03-30T00:00:00+0200",
]
mixed_parsed = (
    pl.Series(data)
    .str.to_datetime("%Y-%m-%dT%H:%M:%S%z")
    .dt.convert_time_zone("Europe/Brussels")
)


print(mixed_parsed)
#shape: (4,)
Series: '' [datetime[μs, Europe/Brussels]]
[
	2021-03-27 00:00:00 CET
	2021-03-28 00:00:00 CET
	2021-03-29 00:00:00 CEST
	2021-03-30 00:00:00 CEST
]

日期时间数据筛选

from datetime import datetime

df = pl.read_csv("./data/apple_stock.csv", try_parse_dates=True)

print(df)
#shape: (100, 2)
┌────────────┬────────┐
│ Date       ┆ Close  │
│ ---        ┆ ---    │
│ date       ┆ f64    │
╞════════════╪════════╡
│ 1981-02-23 ┆ 24.62  │
│ 1981-05-06 ┆ 27.38  │
│ 1981-05-18 ┆ 28.0   │
│ 1981-09-25 ┆ 14.25  │
│ 1982-07-08 ┆ 11.0   │
│ …          ┆ …      │
│ 2012-05-16 ┆ 546.08 │
│ 2012-12-04 ┆ 575.85 │
│ 2013-07-05 ┆ 417.42 │
│ 2013-11-07 ┆ 512.49 │
│ 2014-02-25 ┆ 522.06 │
└────────────┴────────┘

筛选单个日期时间对象

filtered_df = df.filter(
    pl.col("Date") == datetime(1995, 10, 16),
)
print(filtered_df)
#shape: (1, 2)
┌────────────┬───────┐
│ Date       ┆ Close │
│ ---        ┆ ---   │
│ date       ┆ f64   │
╞════════════╪═══════╡
│ 1995-10-16 ┆ 36.13 │
└────────────┴───────┘

筛选一个日期范围

通过 is_between 方法,指定一个范围

filtered_range_df = df.filter(
    pl.col("Date").is_between(datetime(1995, 7, 1), datetime(1995, 11, 1)),
)
print(filtered_range_df)
#shape: (2, 2)
┌────────────┬───────┐
│ Date       ┆ Close │
│ ---        ┆ ---   │
│ date       ┆ f64   │
╞════════════╪═══════╡
│ 1995-07-06 ┆ 47.0  │
│ 1995-10-16 ┆ 36.13 │
└────────────┴───────┘

筛选 负数日期

考古领域数据可能会涉及这类日期

ts = pl.Series(["-1300-05-23", "-1400-03-02"]).str.to_date()

negative_dates_df = pl.DataFrame({"ts": ts, "values": [3, 4]})

negative_dates_filtered_df = negative_dates_df.filter(pl.col("ts").dt.year() < -1300)
print(negative_dates_filtered_df)
#shape: (1, 2)
┌─────────────┬────────┐
│ ts          ┆ values │
│ ---         ┆ ---    │
│ date        ┆ i64    │
╞═════════════╪════════╡
│ -1400-03-02 ┆ 4      │
└─────────────┴────────┘

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

由于昨天利用基础库Matplotlib画的不是很完美,今天借助高级可视化库pyecharts来重新再实现一下人民日报的各国疫情图。

pyecharts简介

Echarts 是一个由百度开源的数据可视化,凭借着良好的交互性,精巧的图表设计,得到了众多开发者的认可。而 Python 是一门富有表达力的语言,很适合用于数据处理。当数据分析遇上数据可视化时,pyecharts 诞生了。

《人民日报》 VS pyecharts制作

数据均是截至到2020年3月14日0时,各国疫情累计确诊人数,可以明显看出人民日报的图可能是PS出来的~~~

人民日报

pyecharts制作

安装pyecharts

官网:https://pyecharts.org/#/

#在终端中安装,或者Anaconda Prompt 中
pip install pyecharts
#conda install pyecharts

#查看是否安装成功
import pyecharts

print(pyecharts.__version__)
# 1.7.1

整个画图代码

#导入pandas
import pandas as pd

#读取数据
data=pd.read_excel('海外疫情.xlsx')

#自定义了一个人民日报的颜色主题
from pyecharts.globals import CurrentConfig
CurrentConfig.ONLINE_HOST = "http://127.0.0.1:8000/assets/"

from pyecharts.datasets import register_files
register_files({"people_COVID": ["themes/people_COVID", "js"]})

#导入pyecharts用到的内容
from pyecharts import options as opts
from pyecharts.charts import Pie

#画图
p=Pie(init_opts=opts.InitOpts(width="1000px", height="1200px",theme='people_COVID'))
p.add('',
      [list(z) for z in zip(data['国家'][:30],data['累计'][:30])],
      radius=["7%", '100%'],   #内半径,外半径
      center=["50%", "60%"],   #中心点的坐标位置
      rosetype="area",        
      is_clockwise=False,   #逆时针
      label_opts=opts.LabelOpts(is_show=False))  #标签显示设置
p.set_global_opts(title_opts=opts.TitleOpts(title="新冠肺炎全球疫情形势",
                                            pos_left="30%",
                                            pos_top="12%",
                                            title_textstyle_opts=opts.TextStyleOpts(font_size=30)
                                           ),
                 legend_opts=opts.LegendOpts(is_show=False)
                 )

p.render_notebook()
# p.render("pie.html")

参考资料

  1. http://gallery.pyecharts.org/#/Pie/pie_rosetype
  2. https://echarts.baidu.com/theme-builder/
  3. https://github.com/pyecharts/pyecharts-assets
  4. https://www.sioe.cn/yingyong/yanse-rgb-16/

以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

中国在经历了非典后(2002年在中国广东发生),今年有经历了新冠肺炎,2020年注定是不平凡的一年。前一段时间人民日报的新冠肺炎全球疫情形势可视化图片在朋友圈疯狂传播,相信大部分人都不陌生,如下所示,自己闲暇之余就想用Python来实现一下,如下所示。

SARS事件是指严重急性呼吸综合征(英语:SARS)于2002年在中国广东发生,并扩散至东南亚乃至全球,直至2003年中期疫情才被逐渐消灭的一次全球性传染病疫潮。

人民日报 pyhton制作

实现过程

  • 数据源

各国的数据在网上都能查到,所以数据来源可以有很多方法。

由于自己主要是想画图,所以就直接手动创建了Excel,手动输入数据。 数据

  • 用到的Python库(轮子 or 模块)

Python在可视化方面有很多的库,比如:Matplotlib、Seaborn、ggplot、Pyecharts等,在这里使用的是最基础的Matplotlib库,数据读取用到的Pandas库,计算时用到Numpy库

  • 具体代码如下所示
#导入相应的库
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

#设置字体,可以在图上显示中文
plt.rcParams['font.sans-serif']=['Microsoft YaHei']   

#读取数据
data=pd.read_excel('海外疫情.xlsx',index_col=0)

#数据计算,这里只取前20个国家
radius = data['累计'][:20]
n=radius.count()
theta = np.arange(0, 2*np.pi, 2*np.pi/n)+2*np.pi/(2*n)    #360度分成20分,外加偏移

#在画图时用到的 plt.cm.spring_r(r)   r的范围要求时[0,1]
radius_maxmin=(radius-radius.min())/(radius.max()-radius.min())  #x-min/max-min   归一化  

#画图
fig = plt.figure(figsize=(20,5),dpi=256)
ax = fig.add_subplot(projection='polar')    #启用极坐标
bar = ax.bar(theta, radius,width=2*np.pi/n)


ax.set_theta_zero_location('N')  #分别为N, NW, W, SW, S, SE, E, NE
ax.set_rgrids([])    #用于设置极径网格线显示
# ax.set_rticks()    #用于设置极径网格线的显示范围
# ax.set_theta_direction(-1)    #设置极坐标的正方向
ax.set_thetagrids([])  #用于设置极坐标角度网格线显示
# ax.set_theta_offset(np.pi/2)       #用于设置角度偏离
ax.set_title('新冠肺炎全球疫情形势',fontdict={'fontsize':8})   #设置标题

#设置扇形各片的颜色
for r, bar in zip(radius_maxmin, bar):
    bar.set_facecolor(plt.cm.spring_r(r))  
    bar.set_alpha(0.8)

#设置边框显示    
for key, spine in ax.spines.items():  
    if key=='polar':
        spine.set_visible(False)

plt.show()

#保存图片
fig.savefig('COVID.png')

总结

目前自己所实现的比较复杂的图形,有罗兰贝格图、本文的南丁格尔玫瑰图,用到的数据知识相对来说比较多,可见数学基础知识是多么重要,华为任正非的做法非常对,必须得注重基础数学的研发,不能总是在别人的基础之上搞应用,华为才有了今天的成绩

相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

最近项目在写报告时需要用到罗兰贝格图进行数据可视化呈现,罗兰贝格图如下所示,是用红色代表相对更强的方面,蓝色代表相对比较弱的方面,比如两个品牌在对比时,A品牌与B品牌,A品牌在哪些方面更有优势,在哪些方便相对比较薄弱。通过罗兰贝格图可以给人直观的印象,可视化出来能够使客户一眼判断出来。 原始罗兰贝格图

Python等高线图

罗兰贝格图放在Python里面对应就是等高线图,等高线图在matplotlib库里面对应plt.contourf函数与plt.contour函数,熟悉里面的参数后基本都可以进行画图,由于我们的数据是一个表格(矩阵),只有对应的几个点,要想画出这种不规则的图,只能利用插值方法进行处理,然后需要把图形转换成平滑的形状。

遇到的难点:

  1. 0应该为白色,需要用来区分红色和蓝色,在画图时需要用自定义函数来处理
  2. 画的图要进行平滑处理,需要利用矩阵二次样条插值法

下面一步一步进行介绍:

  • 1、添加上下边界
    这个需要在Excel里面手动进行处理,放在python中也是可以处理,但是相对来说Excel里面更灵活,后面可以直接进行读取数据,然后画图。目的主要用来画图时,图的周边是白色 添加上下边界

  • 2、读取数据
    从Excel文件里面读取数据 读取数据

  • 3、自定义插值函数
    这里面用的是线性插值,在两个点中间插入一个新的点,新点的值为两边点的均值:$新点值x=\frac{a+b}{2}$,下面一共自定义两个,一个是用来对二维数据进行插值处理,一个是用来对一维数据进行插值 自定义插值函数

  • 4、自定义颜色分割界线函数
    主要用来规避等高线图里面自带的cmp参数的缺点,使0值作为红色与蓝色的对称中心,使红色分为5个区间,蓝色分为5个区间。这个自定义函数里面progression参数可以选择用等差还是等比来分割,默认的是等比,画图结果相对更美观一些 自定义颜色分割界线

  • 5、定义坐标点
    定义坐标点后,并利用上面自定义的插值函数对x、y、Height各进行一次线性插值 定义坐标点

  • 6、进行平滑处理
    运用scipy库里面的矩阵样条插值函数进行平滑度处理,详细参数含义可以查看官方文档https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.RectBivariateSpline.html#scipy.interpolate.RectBivariateSpline

进行平滑处理

  • 7、画图
    首先需要用到上面自定义的颜色分割界线函数计算一下分割界线,然后剩下的和正常的画图一样 画图
  • 8、最终结果
    最终结果

以上为整个罗兰贝格的画图步骤,下面展示全部的详细代码:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import interpolate     #插值引用

plt.rcParams['font.sans-serif']=['Microsoft YaHei']
plt.rcParams['axes.unicode_minus']=False

#加载数据
data=pd.read_excel('红蓝图制作数据.xlsx',sheet_name='python-简书',index_col=0)
data

#自定义插值函数---二维
def extend_data(data):
    data=np.array(data)
    
    def extend_x(X):
        data=X.copy()
        n=data.shape[1]
        mid_extend=np.zeros(shape=(data.shape[0],n-1))
        out=np.zeros(shape=(data.shape[0],2*n-1))

        for i in range(n-1):
            mid_extend[:,i]=(data[:,i]+data[:,i+1])/2.0

        for i in range(2*n-1):
            if i%2==0:
                out[:,i]=data[:,0]
                data=np.delete(data, 0, axis=1)
            else:
                out[:,i]=mid_extend[:,0]
                mid_extend=np.delete(mid_extend, 0, axis=1)

        return out
    
    def extend_y(Y):
        data=Y.T.copy()

        return extend_x(data).T
    
    data=extend_x(data)
    data=extend_y(data)
    
    return data

#自定义插值函数---一维
def extend_x(X):
    data=X.copy()
    n=data.shape[0]
    mid_extend=np.zeros(n-1)
    out=np.zeros(2*n-1)

    for i in range(n-1):
        mid_extend[i]=(data[i]+data[i+1])/2.0

    for i in range(2*n-1):
        if i%2==0:
            out[i]=data[0]
            data=np.delete(data, 0)
        else:
            out[i]=mid_extend[0]
            mid_extend=np.delete(mid_extend, 0)

    return out

#自定义颜色分割界线
def cmap_def(Height,progression='logspace'):
    '''
    white_min  白色下界
    white_max  白色上界,与白色下界对称
    n          在extend_Height最大值与白色上界等分的数量
    progression  'logspace' 等差数列   'logspace'等比数列
    
    返回:levels
    '''
    white_min=-1*np.max(np.abs(Height))/10     #白色下界
    white_max=1*np.max(np.abs(Height))/10      #白色上界,与白色下界对称
    n=6      #在Height最大值与白色上界等分的数量
    
    Height_max=np.max(np.abs(Height))+np.max(np.abs(Height))/10
    
    if progression=='linspace':         #线性等差分割点
        
        levels=list(np.linspace(-1*Height_max,white_min,n))   #小于白色下界值等分n

        levels.extend(list(np.linspace(white_max,Height_max,n)))
    
    else :    #等比分割点
        levels=-1*np.logspace(np.log(white_max),np.log(Height_max),n,base=np.e)
        levels.sort()
        levels=list(levels)
        levels.extend(list(np.logspace(np.log(white_max),np.log(Height_max),n,base=np.e)))
    
    return levels

x=np.array([0,1,2,3,4,5,6])
y=np.array([0,1,2,3,4,5,6])
Height=data.values
#先进行一次线性插值
n=1
x_extend=x.copy()
y_extend=y.copy()
Height_extend=Height.copy()
for i in range(n):
    x_extend=extend_x(x_extend)
    y_extend=extend_x(y_extend)
    Height_extend=extend_data(Height_extend)

#进行矩阵二元样条插值
Height_R= interpolate.RectBivariateSpline(x_extend,y_extend,Height_extend,kx=2, ky=2,s=0.0000001)
x_new=np.linspace(0,6,2000)
y_new=np.linspace(0,6,2000)
X_new,Y_new=np.meshgrid(x_new, y_new)
Height_new=Height_R(x_new,y_new)

#颜色分割界线
levels=cmap_def(Height)

#自定义颜色阶
colors=['#0c7fa4','#3893b4','#6ebcd2','#8ec9d6','#afd5e4','#ffffff','#f4c0bd','#eb827f','#ea5350','#de333a','#d41f26']

#画图
fig=plt.figure(figsize=(12, 6),dpi=255)

# 填充颜色
a=plt.contourf(X_new, Y_new, Height_new, levels=levels, alpha =1,colors=colors)
# 绘制等高线
c= plt.contour(X_new, Y_new, Height_new, levels=levels, colors = 'white', linewidths=0.3,alpha=1,linestyles='solid')

b=plt.colorbar(a,ticks=levels)
b.set_ticklabels(ticklabels=[format(i,'.1%') for i in levels])

plt.xticks([1,2,3,4,5],labels=['性格1','性格2','性格3','性格4','性格5'])
plt.yticks([1,2,3,4,5],labels=['产品人群1','产品人群2','产品人群3','产品人群4','产品人群5'])
plt.tick_params(length=0)  #不显示刻度线

# #去掉顶部、右边 边框
# ax = plt.gca()
# ax.spines['top'].set_visible(False)
# ax.spines['right'].set_visible(False)

plt.xlim(0,6)
plt.ylim(0,6)
# plt.grid()  #网格线

plt.savefig('a_vs_b红蓝图.png',dpi=255)  #保存图片

plt.show()

以上是自己实践中遇到的一些点,分享出来供大家参考学习,欢迎关注本简书号

背景

在复杂的数字中找规律,不如从一张图中看出规律,在平时的汇报时,PPT里面能展示的也就那么几种图表,但是合理的把数据展示出来,有时能让人眼前一亮,在数据分析中合理的运用可视化技术,有时可以起到事半功倍的效果

数据可视化是一门艺术,有时清晰的图表胜过千言万语,数据可视化的成功,往往并不在于数据可视化本身。

效果

效果

利用自定义函数画图

import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.ticker as mticker

#显示所有列
pd.set_option('display.max_columns', None)

#显示所有行
pd.set_option('display.max_rows', None)


#中文乱码的处理
plt.rcParams['font.sans-serif']=['Microsoft YaHei']
plt.rcParams['axes.unicode_minus']=False

data=pd.read_excel('模拟数据.xlsx')

def axes_plot(axs1,axs2,x,y,rotation=0,axs1_twinx=False):
    
    #柱形图个数
    axs1.bar(x,y,width=0.75,align='center')
    for a,b in zip(x,y):
        axs1.text(a,b,b,ha='center',va='bottom')
    axs1.tick_params(axis='x')
    
    #修改x坐标轴
    axs1.xaxis.set_major_locator(mticker.FixedLocator(range(len(x))))
    axs1.set_xticklabels(x,rotation=rotation,fontsize=12)
    
    #累计百分比
    if axs1_twinx:
        axs_twinx=axs1.twinx()
        y_twinx=np.array(y).cumsum()/np.array(y).sum()
        axs_twinx.plot(x,y_twinx,'r-o',linewidth=3)
        axs_twinx.set_ylim(0,1.1)
        
        for a,b in zip(x,y_twinx):
            axs_twinx.text(a,b,f'{b:.0%}',ha='center',va='bottom')
        
    
    #饼图
    axs2.pie(y,labels=x,autopct='%.0f%%',textprops={'fontsize':12,'color':'k'})
    axs2.axis('equal')


%matplotlib inline
fig,axes=plt.subplots(1,2,figsize=(20,7),facecolor='white')
fontsize=15

x=data['地区']
y=data['销量']
axes_plot(axes[0],axes[1],x,y,rotation=45,axs1_twinx=True)

fig.suptitle('各大区销量分布',fontsize=20,fontweight ="bold",y=0.98)
plt.subplots_adjust(hspace=0.35,wspace=0.3)
plt.show()

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

在业务数据统计分析中基本都会涉及到各省区的分析,数据可视化是数据分析的一把利器,这些省区的数据一般会用地图可视化出来,这样一些规律可以被一面了然发现

地图有很多可视化类型,比如:基本地理图、热力图、路径图、涟漪图 等,本篇文章主要介绍 热力图,使用的工具百度开源 pyecharts

模拟数据以十一期间全国旅游景点热度为例(虚构数据) 模拟数据

基于pyecharts内置经纬度的热力图

pyecharts 中自带了一些城市的经纬度,在画图时只要列出城市 or 省份的名字,即可在地图中自动展示,pyecharts会根据城市 or 省份的名字自动提取到经纬度

安装完pyecharts包之后,可以在pyecharts包文件夹里面找到相应的文件 city_coordinates.json ,里面保存了大量的地理名与经纬度的信息 一般路径如下:

xxxxxx\Lib\site-packages\pyecharts\datasets\city_coordinates.json

下面用模拟数据中 城市热度 列来进行热力图可视化

左右滑动查看完整代码

import pandas as pd
from pyecharts import options as opts
from pyecharts.charts import BMap
from pyecharts.globals import BMapType
import json

data=pd.read_excel('热力图模拟数据.xlsx')

hotmap = (
    BMap(is_ignore_nonexistent_coord=True,    #忽略不存在的坐标
         init_opts=opts.InitOpts(width="1300px", height="600px"))
    .add_schema(baidu_ak="自己申请的key", center=[120.13066322374, 30.240018034923],
                zoom=5,   # 当前视角的缩放比例
                is_roam=True   # 是否开启鼠标缩放和平移漫游
               )  
    .add(
        "热度",  #图例
        data_pair=[list(z) for z in zip(data['城市'].to_list(), data['热度'].to_list())],
        type_="heatmap",
        label_opts=opts.LabelOpts(formatter="{b}"),
    )
    .set_global_opts(
        title_opts=opts.TitleOpts(title="十一期间全国旅游景点热度",
                                  pos_left='center',
                                  title_textstyle_opts=opts.TextStyleOpts(font_size=32)
                                 ), 
        legend_opts=opts.LegendOpts(pos_right='20%'),
        visualmap_opts=opts.VisualMapOpts()
    )
    .add_control_panel(
        copyright_control_opts=opts.BMapCopyrightTypeOpts(position=3),
        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(),
        geo_location_control_opts=opts.BMapGeoLocationControlOpts(),
    )
    .render("基于pyecharts内置经纬度的热力图.html")
)

#hotmap.render_notebook()

内置经纬度

基于自定义经纬度的热力图

因pyecharts中的 city_coordinates.json 里面存放的均是一些常用的地理经纬度,如果想使用自定义的经纬度,pyecharts也是支持的

下面用模拟数据中 经度维度热度 列来进行热力图可视化

import pandas as pd
from pyecharts import options as opts
from pyecharts.charts import BMap
from pyecharts.globals import BMapType
import json

data=pd.read_excel('热力图模拟数据.xlsx')

data_json={}
for index,row in data.iterrows():
    data_json[row['地名']]=[float(row['经度']),float(row['维度'])]

with open("BMAP.json","w") as f:
    json.dump(data_json,f)

hotmap = (
    BMap(is_ignore_nonexistent_coord=True,    #忽略不存在的坐标
         init_opts=opts.InitOpts(width="1300px", height="600px"))
    .add_schema(baidu_ak="自己申请的key", center=[120.13066322374, 30.240018034923],
                zoom=5,   # 当前视角的缩放比例
                is_roam=True   # 是否开启鼠标缩放和平移漫游
               )
    .add_coordinate_json("BMAP.json")  #加载自定义坐标
    .add(
        "热度",  #图例
        data_pair=[list(z) for z in zip(data['地名'].to_list(), data['热度'].to_list())],
        type_="heatmap",
        label_opts=opts.LabelOpts(formatter="{b}"),
    )
    .set_global_opts(
        title_opts=opts.TitleOpts(title="十一期间全国旅游景点热度",
                                  pos_left='center',
                                  title_textstyle_opts=opts.TextStyleOpts(font_size=32)
                                 ), 
        legend_opts=opts.LegendOpts(pos_right='20%'),
        visualmap_opts=opts.VisualMapOpts(max_=20)  #设置最大值,目的是为了能够精确查看自定坐标位置
    )
    .add_control_panel(
        copyright_control_opts=opts.BMapCopyrightTypeOpts(position=3),
        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(),
        geo_location_control_opts=opts.BMapGeoLocationControlOpts(),
    )
    .render("基于自定义经纬度的热力图.html")
)

#hotmap.render_notebook()

自定义经纬度

pyecharts库缺点

没有现成的方法用来直接导入自定义坐标,需要先把自定义坐标写在json文件中,然后再通过加载文件实现导入,而没有一个直接导入自定义坐标的方法,这个可以从源码中看出来,如果有一个 add_coordinate_dict 函数就完美了 缺点

不同地图坐标系区别

我们通常用经纬度来表示一个地理位置,但是由于一些原因,我们从不同渠道得到的经纬度信息可能并不是在同一个坐标系下。

  • 高德地图、腾讯地图以及谷歌中国区地图使用的是GCJ-02坐标系
  • 百度地图使用的是BD-09坐标系
  • 底层接口(HTML5 Geolocation或ios、安卓API)通过GPS设备获取的坐标使用的是WGS-84坐标系

不同的坐标系之间可能有几十到几百米的偏移,所以在开发基于地图的产品,或者做地理数据可视化时,我们需要修正不同坐标系之间的偏差。

WGS-84 - 世界大地测量系统

WGS-84(World Geodetic System, WGS)是使用最广泛的坐标系,也是世界通用的坐标系,GPS设备得到的经纬度就是在WGS84坐标系下的经纬度。通常通过底层接口得到的定位信息都是WGS84坐标系

GCJ-02 - 国测局坐标

GCJ-02(G-Guojia国家,C-Cehui测绘,J-Ju局),又被称为火星坐标系,是一种基于WGS-84制定的大地测量系统,由中国国测局制定。此坐标系所采用的混淆算法会在经纬度中加入随机的偏移。

国家规定,中国大陆所有公开地理数据都需要至少用GCJ-02进行加密,也就是说我们从国内公司的产品中得到的数据,一定是经过了加密的。绝大部分国内互联网地图提供商都是使用GCJ-02坐标系,包括高德地图,谷歌地图中国区等。

BD-09 - 百度坐标系

BD-09(Baidu, BD)是百度地图使用的地理坐标系,其在GCJ-02上多增加了一次变换,用来保护用户隐私。从百度产品中得到的坐标都是BD-09坐标系

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

大家在初中时,开始学习圆相关的知识,涉及圆的半径、周长、面积 等等,那会每位同学基本都会买一套圆规、三角板,来辅助学习和做作业使用,这些学习工具在闲暇时光也被用来玩耍,偶然间就拿着圆规在纸上画了这么一个图形,所有的圆心在同一个圆上,该图形一直记忆很深刻。自从学了Python 后就一直有这么一个念头,用Python把它实现出来,最近利用业余时间就给画了出来,分享出来供大家参考学习,也是数据可视化的一部分

效果图: 圆心在同一个圆上

画圆的方法

画圆的方法,参考该篇文章:如何在 Matplotlib 中绘制圆,该文章一共介绍了3种方法,其中第2种方法:在 Matplotlib 中用圆方程绘制圆,可能有点不好理解,这里小编专门绘制了一个图来做解释,大家看了后应该可以理解

文章地址:https://www.delftstack.com/zh/howto/matplotlib/how-to-plot-a-circle-in-matplotlib/

圆方程示例

圆方程示例代码:

import numpy as np
from matplotlib import pyplot as plt


figure, axes = plt.subplots()
draw_circle = plt.Circle((0, 0), 1,fill=False,linewidth=2)
axes.set_aspect(1)
axes.add_artist(draw_circle)

#设置上边和右边无边框
axes.spines['right'].set_color('none')
axes.spines['top'].set_color('none')

#设置坐标轴位置
axes.spines['bottom'].set_position(('data', 0))
axes.spines['left'].set_position(('data',0))

plt.plot([0, np.cos(1/6*np.pi)], [0, np.sin(1/6*np.pi)],'r')
plt.plot([np.cos(1/6*np.pi), np.cos(1/6*np.pi)], [0, np.sin(1/6*np.pi)],'b--')
plt.xticks([])
plt.yticks([])
plt.xlim(-1.5,1.5)
plt.ylim(-1.5,1.5)

plt.text(x=0.4,y=0.3,s='$r$')
plt.text(x=0.2,y=0.02,s='$\\theta$')
plt.text(x=0.1,y=-0.1,s='$x=r * cos(\\theta)$',fontsize='small')
plt.text(x=1.,y=0.15,s='$y=r * sin(\\theta)$',fontsize='small')

plt.show()

初中时圆规画的图

圆心在同一个圆上代码:

import numpy as np
from matplotlib import pyplot as plt

theta = np.linspace(0, 2*np.pi, 25)  #生成一些数据,用来计算圆上的点
radius = 2  #半径

x = radius*np.cos(theta)  #圆心的横坐标 x
y = radius*np.sin(theta)  #圆心的横坐标 y

figure, axes = plt.subplots(1,1,figsize=(20,7),facecolor='white',dpi=500)
for circle_x,circle_y in zip(x,y):
    draw_circle = plt.Circle((circle_x, circle_y), radius,fill=False)  #画圆
    axes.add_artist(draw_circle)  
    
axes.set_aspect(1)
plt.xlim(-5,5)
plt.ylim(-5,5)
plt.axis('off')
plt.show()

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

动漫效果的头像最近比较火,微信里面有大量的朋友都是使用这种风格的头像,在一些软件里面也慢慢开始集成该功能,在手机里面可以直接制作出动漫效果的图片

这种风格的图片是怎么生成的呢,那就不得不说最近这几年大火的AI,也就是神经网络模型,可以用来处理目前的一些问题,比如:自然语言/NLP类、图像/CV类、声音类 等,动漫图片就归属于图像/CV类中的一种,本篇文章主要是介绍一个开源的模型,来生成这种动漫效果的图片

动漫效果

动漫效果

开源项目介绍

最近发现一个开源项目可以实现该功能,其模型的权重只有 8.2M ,相对一些大的模型来说已经很轻量级了,并且该模型在自己的笔记本电脑能够很好运行,你可以直接下载该项目到自己的电脑里面,来处理自己想要的图片

pytorch版本地址:(本文基于pytorch版本)
https://github.com/bryandlee/animegan2-pytorch

tensorflow版本地址:
https://github.com/TachibanaYoshino/AnimeGANv2

开源项目

自己动手实践

本篇文章里面的代码是依赖开源项目中的 模型框架: model.py 模型权重: weights文件夹里面的4个权重文件 其他的文件没有涉及,下载开源项目后,用以下的脚本在开源项目里面运行后,生成的结果图片会放在当前目录的 animegan_outs 里面,运用模型的4个不同权重,最终会生成4张不同风格的动漫图片

需要的环境,第三方库:pytorchopencv

import torch
import cv2
import numpy as np
import os
import shutil
from model import Generator
from torchvision.transforms.functional import to_pil_image
from PIL import Image


def load_image(image_path):
    img = cv2.imread(image_path).astype(np.float32)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = torch.from_numpy(img)
    img = img/127.5 - 1.0
    
    return img

image = load_image('yangmi.jpg')   #RGB  此处需要加载自己的图片
#image.shape  #HWC 

models=['face_paint_512_v2','face_paint_512_v1','paprika','celeba_distill']
models_path=[f'./weights/{model}.pt' for model in models]

if os.path.exists('./animegan_outs/'):
    shutil.rmtree('./animegan_outs')
    os.makedirs('./animegan_outs/')
else:
    os.makedirs('./animegan_outs/')

device='cpu'
net = Generator()
images=[]
for model,model_path in zip(models,models_path):
    net.load_state_dict(torch.load(model_path, map_location="cpu"))
    net.to(device).eval()
    
    with torch.no_grad():
        input = image.permute(2, 0, 1).unsqueeze(0).to(device)   #BCHW
        out = net(input, False).squeeze(0).permute(1, 2, 0).cpu().numpy()  #HWC
        out = (out + 1)*127.5
        out = np.clip(out, 0, 255).astype(np.uint8)
        
        pil_out=to_pil_image(out)
        
        pil_out.save(f'./animegan_outs/{model}.jpg')   #保存处理过后的图片
        images.append(pil_out)


font=cv2.FONT_HERSHEY_SIMPLEX    #使用默认字体
UNIT_WIDTH_SIZE,UNIT_HEIGHT_SIZE=images[0].size[:2]

images_add_font=[]      #保存加了文字之后的图片
for model,image in zip(models,images):
    image_font=cv2.putText(np.array(image),model,(280,50),font,1.2,(0,0,0),3)  
    #添加文字,1.2表示字体大小,(280,50)是初始的位置,(0,0,0)表示颜色,3表示粗细
    
    images_add_font.append(Image.fromarray(image_font))

target = Image.new('RGB', (UNIT_WIDTH_SIZE * 2, UNIT_HEIGHT_SIZE * 2))   #创建成品图的画布
#第一个参数RGB表示创建RGB彩色图,第二个参数传入元组指定图片大小,第三个参数可指定颜色,默认为黑色
for row in range(2):
    for col in range(2):
        #对图片进行逐行拼接
        #paste方法第一个参数指定需要拼接的图片,第二个参数为二元元组(指定复制位置的左上角坐标)
        #或四元元组(指定复制位置的左上角和右下角坐标)
        target.paste(images_add_font[2*row+col], (0 + UNIT_WIDTH_SIZE*col, 0 + UNIT_HEIGHT_SIZE*row))

target.save('./animegan_outs/out_all.jpg', quality=100) #保存合并的图片

完整代码

生成的动漫图片

原始图片

face_paint_512_v2

face_paint_512_v1

paprika

celeba_distill

out_all

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

最近项目需要用人脸检测技术把视频里面的人脸检测出来后,进行马赛克处理,人脸检测这一块就是大家熟知的利用深度学习技术来解决

之前有相关文章介绍,这篇文章主要介绍马赛克处理过程

Python 人脸检测方法总结

马赛克原理

图片是由一个三维数组,打马赛克就是把特定区域的值替换为其他值,项目在做的过程中经过一次升级,最开始用的是高斯马赛克,后来应客户的要求,升级为和其他软件手工打的马赛克一样的样式正规马赛克

  • 高斯马赛克

特定区域值替换为高斯分布数值,可以利用numpy中的np.random.normal(size=(h,w))来生成一些随机的数值,然后进行替换即可

  • 正规马赛克

马赛克的实现原理是把图像上某个像素点一定范围邻域内的所有点用邻域内左上像素点的颜色代替,这样可以模糊细节,但是可以保留大体的轮廓。就是用左上角的那个值,来替换右下方一个小方块的值,逐步进行替换即可。

代码

  • 高斯马赛克
import cv2
import numpy as np

face_location=[430,500,730,870]  #x1,y1,x2,y2  x1,y1为人脸左上角点;x2,y2为人脸右下角点
img=cv2.imread('./tongliya.jpg')  #opencv读取的是BGR数组

##高斯马赛克
def normal_mosaic(img, x1, y1, x2, y2):
    img[y1:y2, x1:x2, 0] = np.random.normal(size=(y2-y1, x2-x1))
    img[y1:y2, x1:x2, 1] = np.random.normal(size=(y2-y1, x2-x1))
    img[y1:y2, x1:x2, 2] = np.random.normal(size=(y2-y1, x2-x1))
    
    return img

x1=face_location[0]
y1=face_location[1]
x2=face_location[2]
y2=face_location[3]
img_mosaic=normal_mosaic(img, x1, y1, x2, y2)
cv2.imwrite('img_mosaic_normal.jpg',img_mosaic)
  • 正规马赛克
import cv2
import numpy as np

face_location=[430,500,730,870]  #x1,y1,x2,y2  x1,y1为人脸左上角点;x2,y2为人脸右下角点
img=cv2.imread('./tongliya.jpg')  #opencv读取的是BGR数组

#正规马赛克
def do_mosaic(img, x, y, w, h, neighbor=9):
    """
    :param rgb_img
    :param int x :  马赛克左顶点
    :param int y:  马赛克左顶点
    :param int w:  马赛克宽
    :param int h:  马赛克高
    :param int neighbor:  马赛克每一块的宽
    """
    for i in range(0, h , neighbor):  
        for j in range(0, w , neighbor):
            rect = [j + x, i + y]
            color = img[i + y][j + x].tolist()  # 关键点1 tolist
            left_up = (rect[0], rect[1])
            x2=rect[0] + neighbor - 1   # 关键点2 减去一个像素
            y2=rect[1] + neighbor - 1
            if x2>x+w:
                x2=x+w
            if y2>y+h:
                y2=y+h
            right_down = (x2,y2)  
            cv2.rectangle(img, left_up, right_down, color, -1)   #替换为为一个颜值值
    
    return img

x=face_location[0]
y=face_location[1]
w=face_location[2]-face_location[0]
h=face_location[3]-face_location[1]
img_mosaic=do_mosaic(img, x, y, w, h, neighbor=15)
cv2.imwrite('img_mosaic.jpg',img_mosaic)

效果

原图
高斯马斯克
正规马赛克

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号DataShare,不定期分享干货

背景

大家在制作视频时,是不是见过一种特效:图片从清晰状态慢慢渐变为模糊状态,视频其实也就是每一帧图片拼接组成,今天就来介绍下怎么把图片模糊化,主要运用的原理就是多维高斯滤波器

效果展示

效果展示

小编环境

import sys

print('python 版本:',sys.version.split('|')[0])   
#python 版本: 3.11.4

完整代码

%matplotlib qt
from scipy import ndimage
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np

#中文乱码的处理
plt.rcParams['font.sans-serif']=['Microsoft YaHei']

def blur_image(image_path):    
    image=np.array(Image.open(image_path))
    image = np.rot90(image,-1)  #对图片顺时针旋转90度
    
    plt.figure(figsize=(5, 15),dpi=100)
    plt.subplot(221)
    plt.imshow(image)
    plt.title("原始图片")
    plt.axis( 'off')
    
    sigma = 5
    blurred_image = np.zeros(image.shape, dtype=np.uint8)
    for i in range(3):  #对图像的每一个通道都应用高斯滤波
        blurred_image[:,:,i] = ndimage.gaussian_filter(image[:,:,i], sigma)
    plt.subplot(222)
    plt.imshow(blurred_image)
    plt.title(f'模糊化的图像 (sigma={sigma})')
    plt.axis('off')
    
    sigma = 10
    blurred_image = np.zeros(image.shape, dtype=np.uint8)
    for i in range(3):  #对图像的每一个通道都应用高斯滤波
        blurred_image[:,:,i] = ndimage.gaussian_filter(image[:,:,i], sigma)
    plt.subplot(223)
    plt.imshow(blurred_image)
    plt.title(f'模糊化的图像 (sigma={sigma})')
    plt.axis('off')
    
    sigma = 20
    blurred_image = np.zeros(image.shape, dtype=np.uint8)
    for i in range(3):  #对图像的每一个通道都应用高斯滤波
        blurred_image[:,:,i] = ndimage.gaussian_filter(image[:,:,i], sigma)
    plt.subplot(224)
    plt.imshow(blurred_image)
    plt.title(f'模糊化的图像 (sigma={sigma})')
    plt.axis('off')
    
    plt.subplots_adjust(top=0.9,
                        bottom=0.1,
                        left=0.125,
                        right=0.9,
                        hspace=0.05,
                        wspace=0.07)
    plt.show()


image_path ='秋天的银杏树.jpg'
blur_image(image_path)

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

在对视频进行人脸打码时,需要从磁盘读取视频帧,然后通过训练好的深度神经网络模型进行人脸监测,获取到人脸的位置后,然后进行打码。

opencv读取多张视频帧,提高性能

由于opencv每次只能读取一张视频帧,然后把这一张视频帧送入神经网络模型进行人脸监测,这样逐帧的处理视频,速度相对来说比较慢。

为了提高性能,需要进行优化。如果对训练深度神经网络模型,原理了解的话,那么可以每次传入多个视频帧,这样每次作为一个batch,使计算效率更高一些。深度神经网络模型在训练时,是每次处理一个batch图像,来通过梯度下降,优化模型参数。

这样就需要opencv每次读取多个视频帧,但是opencv里面没有这样的方法,只能自己去实现这样的方法。

实例代码

import cv2
import numpy as np

video_full_path_and_name='./test.mp4'
videoCapture = cv2.VideoCapture()  # 创建视频读取对象
videoCapture.open(video_full_path_and_name)  # 打开视频
fps = int(round(videoCapture.get(cv2.CAP_PROP_FPS),0))
image_width=int(videoCapture.get(cv2.CAP_PROP_FRAME_WIDTH))   #视频帧宽度
image_height=int(videoCapture.get(cv2.CAP_PROP_FRAME_HEIGHT))   #视频帧高度
image_channel=3   #RGB 3通道

ret=True
while ret:
	img_batch_rgb=np.empty(shape=[0,image_height,image_width,image_channel],dtype=np.uint8)
	#每次读取1s的视频帧,可以根据自己的内存大小,每次读取多秒
	for i in range(fps*1):
		ret, img = videoCapture.read()
		#读取到图像帧   
		if ret:
			# opencv:BGR  转换为 RGB
			rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
			img_batch_rgb=np.append(img_batch_rgb,np.expand_dims(rgb_img, 0),axis=0)
		else:
			break

print(img_batch_rgb.shape,flush=True)
#img_batch_rgb      #该变量即为多个视频帧

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号,不定期分享干货

背景

由于最近项目的要求,需要对视频里面出现的所有人脸进行打马赛克,调研了相关的方法,总结了一下,供大家参考。 大家都知道视频其实是有很多张图片(帧)组成的,那么只要把图片中的人脸检测出来,那么视频就迎刃而解了。

帧数就是在1秒钟时间里传输的图片的量,也可以理解为图形处理器每秒钟能够刷新几次,通常用fps(Frames Per Second)表示。每一帧都是静止的图象,快速连续地显示帧便形成了运动的假象。高的帧率可以得到更流畅、更逼真的动画。帧数 (fps) 越高,所显示的动作就会越流畅。 但是文件大小会变大

人脸检测 vs 人脸识别

人脸检测人脸识别 是两个不同的概念

人脸检测

检测图片里面有没有人脸

人脸识别

首先检测出图片中的人脸,再与已知的人脸做对比,看这个人脸是张三的,还是李四的,常见应用:人脸识别上班打卡

方法总结

  • opencv

最简单、速度最快的方法,但效果一般

  • face_recongnition(基于dlib)

可选参数 hog 、cnn

pip有发布的库,不用自己训练模型,可以直接拿来使用,比较方便,效果较好 https://github.com/ageitgey/face_recognition face_recognition模块方法集合

  • mtcnn(基于tensorflow)

需要训练自己的模型,github有开源的项目,效果较好 https://github.com/ipazc/mtcnn

  • RetinaFace(基于pytorch)-----------最终采用

需要训练自己的模型,github有开源的项目,效果最好 https://github.com/supernotman/RetinaFace_Pytorch

以上这些都是自己亲自试验过的人脸检测方法,RetinaFace(基于pytorch) 方法效果最好,最终采用这种方法,进行了一些优化

  • 其他一些方法 方法

RetinaFace效果

低难度 中难度 高难度

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号,不定期分享干货

背景

前段时间因为要办理一些事情,需要家里人拍 户口本首页个人所在页 的照片用来打印,家里父亲已经年过六旬,能学会玩微信已经实属不易,让父亲用手机拍出很正的图片有点太难,户口本首页 拍了有5张以上,里面有一张比较清晰,能凑合着用,但还是不正,于是想着能不能 旋转 一下,让照片变正。因之前对opencv有所了解,就查找相关文档,然后对图片进行处理,3步即可搞定,最后大功告成!!! 处理对比.png

基于opencv图片处理过程

1.安装opencv

由于opencv为第三方库,需要手动安装一下,安装成功后即可导入使用

pip install opencv-python

2.确定照片上4个点

可以想象一下,把照片看成一个空间,要想把照片旋转正,我们可以拽着户口本首页4个角,给拽到图片的4个角,有点立体的感觉,如下图所示: 确定4个点

确定照片上这4个点的方法比较多,比如:我们可以直接用尺子测量一下、根据截图大小计算一下等。

但是作为会编程的人员,我们可以有更好的方法:点击图片上的4个点,可以直接出现相应的位置(x坐标,y坐标)

经过在网上查找了相关的帖子后,opencv就可以实现这个功能,结果如下:

4个点的位置

代码如下:

#加载库
import cv2
import numpy as np

img = cv2.imread('home-1.jpg')
img.shape    #图片尺寸大小
#(1080, 1456, 3)

#定义函数,实现在图片上显示坐标位置
def on_EVENT_LBUTTONDOWN(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDOWN:
        xy = "(%d,%d)" % (x, y)
        print(xy)
        cv2.circle(img, (x, y), 10, (0, 176, 80), thickness = -1)
        if (x+50)>img.shape[1] & x>(img.shape[1]/2):
            cv2.putText(img, xy, (x-180, y-10), cv2.FONT_HERSHEY_PLAIN,2.0, (0,0,255), thickness = 2)
        elif x<50:
            cv2.putText(img, xy, (x, y-10), cv2.FONT_HERSHEY_PLAIN,2.0, (0,0,255), thickness = 2)
        else:
            cv2.putText(img, xy, (x-20, y-10), cv2.FONT_HERSHEY_PLAIN,2.0, (0,0,255), thickness = 2)
        cv2.imshow("image", img)

cv2.namedWindow("image",0)
cv2.resizeWindow("image", 640, 480)
cv2.setMouseCallback("image", on_EVENT_LBUTTONDOWN)
cv2.imshow("image", img)
while(True):
    try:
        cv2.waitKey(10)
    except:
        cv2.destroyWindow("image")
        break
        
cv2.waitKey(10)
cv2.destroyAllWindow()

代码执行过程中,唯一不足一点为,最后需要手动强制退出程序,结束循环

3.旋转图片

根据上面大概确定好4个点的位置后,下面调用opencv的透视变换函数cv2.warpPerspective,对进行图片旋转,旋转时坐标位置可以根据实际结果进行调节,是否想多显示一点边界,可以调整x,y坐标大小,旋转后效果如下,照片基本算是比较平整,没有出现明显的波纹,打印出来可以用

旋转后图片

代码如下:

import cv2
import numpy as np

img = cv2.imread('home-1.jpg')

#确定位置,旋转前后位置要相互对应,坐标位置可以根据实际来调节
pts1 = np.float32([[202,70],[120,1040],[1415,135],[1435,1055]])   #左上  左下   右上   右下
pts2 = np.float32([[0,0],[0,1080],[1456,0],[1456,1080]])

#进行旋转
M = cv2.getPerspectiveTransform(pts1,pts2)
home_P = cv2.warpPerspective(img,M,(cols,rows))

#保存旋转后的图片
cv2.imwrite("home_P.jpg", home_P, [int(cv2.IMWRITE_JPEG_QUALITY), 100])
  • 参考文章
  1. https://blog.csdn.net/huzhenwei/article/details/82900715
  2. https://segmentfault.com/a/1190000015645951

以上是自己实践中遇到的一些点,分享出来供大家参考学习,欢迎关注本简书号

声明:

本篇文章仅用来技术分享,如有侵权请及时联系本小编,微信公众号:DataShare,进行删除

背景

一个用了十年的电影、电视剧网址:飘花电影网,https://www.piaohua.com,关键是一直免费,从大学时代到工作了几年后,仍一直在用的网站,里面的电影、电视剧一直实时更新,各大平台的VIP内容均可免费观看。 电影

电视剧

最开始是支持迅雷下载,后来迅雷会屏蔽下载链接,现在是网站自己开发一个 荐片播放器 可以直接用来边下载边观看,很是方便

荐片播放器下载地址:https://www.jianpian8.com 荐片播放器下载地址

分析抓包内容

第一集:ftp://a.gbl.114s.com:20320/1531/人世间第1集.mp4

第二集:ftp://a.gbl.114s.com:20320/0999/人世间第2集.mp4

第三集:ftp://a.gbl.114s.com:20320/8950/人世间第3集.mp4

通过上面每一集的地址,可以发现 a.gbl.114s.com 这个服务器地址很牛逼,存放了这么多免费的电影、电视剧,于是通过国外一个搜索引擎找到了一个开放的API,可以查看播放地址

API地址:https://api2.rinhome.com/api/node/detail?id=556881

返回的json

可以明确《人世间的》的 id 是 556881,于是脑洞大开,想从id=1开始到100万,采集一个完整的明细,包含电影、电视剧

完整代码

import asyncio
import aiohttp
import random
import json
import time


urls=[(id,f'https://api2.rinhome.com/api/node/detail?id={id}') for id in range(1,1000000)]

# User-Agent
def randomheader():
    UA = [
        'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',
        'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1',
        'Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11',
        'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6',
        'Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6',
        'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1',
        'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5',
        'Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5',
        'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3',
        'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3',
        'Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3',
        'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3',
        'Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3',
        'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3',
        'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3',
        'Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3',
        'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24',
        'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24',
        'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36'
        ]
    headers = {'user-agent': random.choice(UA)}
    headers[
        'accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'
    headers['accept-encoding'] = 'gzip, deflate, br'
    headers['accept-language'] = 'zh-CN,zh;q=0.9'
    headers['cache-control'] = 'max-age=0'
    headers['sec-ch-ua'] = '" Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"'
    headers['sec-ch-ua-mobile'] = '?0'
    headers['sec-ch-ua-platform'] = '"Windows"'
    headers['sec-fetch-dest'] = 'document'
    headers['sec-fetch-mode'] = 'navigate'
    headers['upgrade-insecure-requests'] = '1'
    headers['sec-fetch-site'] = 'none'
    headers['sec-fetch-user'] = '?1'

    return headers

async def async_get_url(id,url,sem):
    async with sem:
        async with aiohttp.ClientSession() as session:
            #print(randomheader())
            async with session.get(url,headers=randomheader()) as r:
                print('正在采集:', url)
                html = await r.text()
                try:
                    text_json = json.loads(html)
                    if text_json['msg'] == '请求成功':
                        with open(f'./success-async/{id}.json', 'w', encoding='utf-8') as fp:
                            json.dump(text_json, fp, ensure_ascii=False, indent=4)
                    else:
                        with open(f'./fail-async/{id}.json', 'w', encoding='utf-8') as fp:
                            json.dump(text_json, fp, ensure_ascii=False, indent=4)
                except:
                    with open(f'./fail-async/{id}.json', 'w', encoding='utf-8') as fp:
                        fp.write(html)
                        
                        
if __name__=='__main__':
    start=time.time()

    sem = asyncio.Semaphore(300)   #协程并发任务量
    tasks = [async_get_url(id, url, sem) for id, url in urls]
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()

    print(f'cost time: {time.time() - start}s')

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

从小编真实接触股票已经有10年之久了,因为大学的专业就是数据与应用数据(金融学方向),大三、大四学期时学习了很多涉及金融相关的课程,特别是在大四时,老师还专门给每位同学开通了模拟炒股的账户,让全班同学一起模拟炒股,但小编用真金白银炒股的时间大概是2018年,居现在也有5年时间,一直是韭菜中

最近大家也看到了曾任《环球时报》总编辑的胡锡进,也开始入市炒股,并且每天都会发博文,分享当天的炒股感受

于是小编就试着获取股票的数据来研究一下,经过查找与对比,小编决定用akshare这个库,因为该库一直有更新,并且文档是中文,而且比较详细, 各种数据

akshare文档地址:https://www.akshare.xyz/ akshare

股票各种数据获取方法

导入akshare库

import akshare as ak
import pandas as pd

1、股票的基本信息数据

ak.stock_individual_info_em(symbol="000651")

基本信息

2、实时数据,当日的成交数据

单次返回所有沪深京 A 股上市公司的实时行情数据

ak.stock_zh_a_spot_em()   

实时数据

3、历史数据,历史的成交数据

ak.stock_zh_a_hist(symbol="000651", 
                   period="daily", 
                   start_date="20230701", 
                   end_date='20230725', 
                   adjust=""   #不复权
                  )  

历史数据

4、资金流向数据 限量: 单次获取指定市场和股票的近 100 个交易日的资金流数据

ak.stock_individual_fund_flow(stock="000651", market="sz")

资金流向数据

5、行情报价,买卖各5档

ak.stock_bid_ask_em(symbol="000651")

行情报价

每日特定股票数据汇总案例

下面展示每日获取特定股票数据,可以做成定时任务,在15:00闭市后获取

"""
===========================
@Time : 2023/7/26 20:13
@File : stock_day
@Software: PyCharm
@Platform: Win10
@Author : DataShare
===========================
"""
import akshare as ak
import pandas as pd
import datetime
import sys

def stock_to_excel(stock_code, stock_name):
    if stock_code[0] == '6':
        market = 'sh'
    elif stock_code[0] == '0':
        market = 'sz'

    df1 = ak.stock_zh_a_spot_em()
    df2 = df1[df1['代码'] == stock_code]

    dt = str(datetime.date.today())   #当日
    df3 = ak.stock_individual_fund_flow(stock=stock_code, market=market) #在15:00之后获取
    df4 = df3[df3['日期'] == dt]

    result = {
        "日期": dt,
        "股票代码": stock_code,
        "股票名称": stock_name,
        "前一日收盘价": df2['昨收'].to_list()[0],
        "开盘": df2['今开'].to_list()[0],
        "收盘": df2['最新价'].to_list()[0],
        "最高": df2['最高'].to_list()[0],
        "最低": df2['最低'].to_list()[0],
        "成交量": df2['成交量'].to_list()[0],
        "成交额": df2['成交额'].to_list()[0],
        "振幅": df2['振幅'].to_list()[0],
        "涨跌幅": df2['涨跌幅'].to_list()[0],
        "涨跌额": df2['涨跌额'].to_list()[0],
        "换手率": df2['换手率'].to_list()[0],
        "量比": df2['量比'].to_list()[0],
        "市盈率-动态": df2['市盈率-动态'].to_list()[0],
        "市净率": df2['市净率'].to_list()[0],
        "60日涨跌幅": df2['60日涨跌幅'].to_list()[0],
        "主力净流入-净额": df4['主力净流入-净额'].to_list()[0],
        "主力净流入-净占比": df4['主力净流入-净占比'].to_list()[0],
        "超大单净流入-净额": df4['超大单净流入-净额'].to_list()[0],
        "超大单净流入-净占比": df4['超大单净流入-净占比'].to_list()[0],
        "大单净流入-净额": df4['大单净流入-净额'].to_list()[0],
        "大单净流入-净占比": df4['大单净流入-净占比'].to_list()[0],
        "中单净流入-净额": df4['中单净流入-净额'].to_list()[0],
        "中单净流入-净占比": df4['中单净流入-净占比'].to_list()[0],
        "小单净流入-净额": df4['小单净流入-净额'].to_list()[0],
        "小单净流入-净占比": df4['小单净流入-净占比'].to_list()[0]
    }

    return result


if __name__ == '__main__':
    stocks_code = {'000651': '格力电器',
                   '002241': '歌尔股份',
                   '002739': '万达电影',
                   '600956': '新天绿能',
                   '600031': '三一重工',
                   '600703': '三安光电',
                   '002027': '分众传媒',
                   '600030': '中信证券',
                   '002939': '长城证券',
                   }   #小编买过的股票
    
    dt = str(datetime.date.today())
    results=[]
    for stock_code, stock_name in stocks_code.items():
        print(f'{stock_name}:{stock_code}')
        try:
            results.append(stock_to_excel(stock_code, stock_name))
        except Exception as e:
            print("运行中出错",e)
            sys.exit(-1)
    
    pd.DataFrame.from_dict(results).to_excel(f'./data/{dt}.xlsx', index=False)

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

引言

之前分享的大部分内容都是一些技术、方法类文章,今天为大家带来一篇业务方面的文章——增长引擎说,希望大家能够喜欢。

自己始终坚信,技术的升级改造,最终还是服务于业务,如果离开了业务谈技术,那么这种技术最终会因没有用武之地,而被抛弃。所以,我们平时也不能一股脑,把时间全部用在技术上,要想成为一个真正的数据分析师、数据科学家,那么我们也要思考思考当今业务的一些难题。

说到业务,一个重大的难题就是增长,中国现在逐渐失去了人口红利,业务增长这个大难题,就显的更加突出。

《精益数据分析》这本书是自己去年买的,一直放到今年才来看,有点汗颜。。。。。,最近几周看了里面的一些章节,认为讲的很有道理,于是就借来分享一下,供大家参考。

驱动创业增长的三大引擎

  • 黏着式增长引擎
  • 病毒式增长引擎
  • 付费式增长引擎

黏着式增长引擎

黏着式增长引擎的重点是让用户成为回头客,并且持续使用你的产品, 它和提高留存率这个概念类似。如果你的用户黏性不大,流失率会很高,用户参与度就不理想。用户参与度是预测产品成功的最佳指标之一,Facebook早期的用户数并不多(仅限哈佛大学),但它可以在上线数月,就让一个学校几乎全部的学生都变成自己的用户,并持续使用。Facebook的黏性是前所未有的。

衡量黏性最重要的 KPI 就是客户留存率。除此之外,流失率和使用频率也是非常重要的指标。

长期黏性往往来自用户在使用产品过程中为自身所创造的价值。人们很难放弃使用Gmail,因为那里存储了他们所有的资料。同样,让一个玩家在一款大型多人网络游戏中删号,也会是一件非常艰难的决定,因为他将失去在游戏中辛苦赢得的一切地位和虚拟物品。

衡量黏性也不能全看留存率,它还和频率有关,这解释了为什么你需要跟踪“距上次登陆的时间”(RFM模型)这样的指标。如果你使用了提高用户回访的方法,诸如邮件提醒和更新,那么邮件的打开率和点击率也同样需要关注。

病毒式增长引擎

所谓病毒式传播归根结底就是一件事情:让名声传播出去。 病毒式传播之所以吸引人,在于它的指数本质:如果每个用户能带来 1.5 个新用户,那么用户数将会无限制地增长直到饱和。(但事情绝不可能这么简单,用户流失、竞争对手和其他因素决定了它不可能真的无限制增长)

此引擎的关键指标是病毒式传播系数,即每个用户所带来的新用户数。 因为这是一个利滚利的模式(老用户所带来的新用户,同样也会带来更多的用户),这个指标所衡量的是每一个病毒传播周期的新用户量。增长在病毒式传播系数大于1时自发地到来,但你同时也需要考虑流失率对整体病毒因子的影响。病毒因子越大,增长也就越迅速。

仅考虑病毒式传播系数还不够,你还需要衡量哪些用户行为形成了一个病毒传播周期(循环)。例如,大部分社交网络都会在你注册时询问是否要同步你的邮箱通讯录,然后诱导你邀请通讯录里的联系人。这些联系人收到你发出的邀请邮件,可能会欣然接受。这些独立的行为连结在一起,决定着社交网络的病毒性。所以说,衡量这些行为能够让你知道如何将病毒式增长引擎开足马力:改变邀请信里的信息,简化注册流程,等等。

还有其他一些因素也与病毒性相关,包括用户完成一次邀请所需的时间(或叫病毒传播周期)以及病毒性的类别。

付费式增长引擎

第三种驱动增长的引擎是付费。通常,在确知产品具有黏着性和病毒性前就开动这一引擎,是过于仓促的行为。由 Meteor Entertainment 公司开发的《机甲世界》是一款免费多人游戏,但它靠游戏内的增值服务赚钱。这家公司首先专注于提高 beta 测试小组的使用量(黏着性),然后致力于游戏的病毒性(邀请朋友来玩),最后才是付费(玩家购买增值服务的目的是游戏中处于更有利的地位或提升游戏体验)。

从某种程度上讲,赚钱是识别一个商业模式是否可持续的终极指标。如果你从客户身上所赚的钱超过获取客户的花费,并且可以一直这样做下去,你就是可持续的。你不需要外部投资者的钱,并且每天都在增加股东的权益。

但是,就其本身而言,赚钱并不是一种驱动增长的引擎。它只是让银行里的钱越来越多。只有当你反过头来把一部分营收再用于获取客户时,营收才有助于你的增长,然后你就有了一个可调节的业务增长机器。

机器上的两个调节旋钮是客户终生价值(CLV)和客户获取成本(CAC)。 从客户身上赚到的钱比获取他们花掉的钱多自然是一件好事,但这并不简单地等同于成功。你仍需为现金流和增长速度发愁,这取决于多久才能让一个客户付清你获取他所花的成本。一种衡量方法是看客户盈亏平衡时间,也就是你收回获取一位客户的成本所需的时间。


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

最近在做业务分析,用到的方法大家可能都比较熟悉:漏斗分析,这个分析方法在很多业务场景都有应用到,是业务侧的常用的分析方法,除了此之外还有一些业务侧常用的分析方法,比如:5W2H、AAARR 等

但是,熟悉本公众号的读者,可能比较了解公众号分享的都是一些偏技术侧的知识,之前分享过一些算法模型,包括统计学、特征工程、决策树、人脸监测、NLP 等一些机器学习文章,既有传统的机器学习内容,也有深度学习的内容,这些都是偏向技术侧的分析方法

有的同学可能一直在自学机器学习,不知道怎么拿来用到真实的业务中,于是就是产生怎么把业务侧分析方法与技术侧的分析方法结合起来呢?

个人粗浅的理解

业务侧分析方法是一个大框架,技术侧分析方法属于细节分析

要想运用技术侧的分析方法,则需要把业务侧的需求进行拆分,逐步转化为数学问题,然后运用技术侧的方法,但是,业务侧的分析大框架是不能忽略的,必须要有的。就好比盖房子,业务侧分析方法是房子的大框架,技术侧分析方法是在大框架中填充砖头。

比如最近在做的分析,业务侧需要一些高意向客户,进行营销,运用的是漏斗分析的方法, 投放数量--->点击数量--->意向数量--->成交数量,是一个层层递进的漏斗,每两层之间有一个转化率,只有把每层的转化率都提高了,才能达到最终的目的,成交的客户数才能提升。

投放数量--->点击数量,会生成点击率指标,那怎么提升点击率呢?那就需要用ABtest这种统计学方法进行测试,看哪种宣传文案比较好,或者 运用各种机器学习模型,预测哪些用户有可能会点击,对这部分用户进行不同渠道的触达,来提升点击率

但是也有同学会问,不能直接使用机器学习模型筛选一批高意向用户,不用漏斗分析吗? 最开始也是这么实践的,但是效果一直不好,原因就是虽然投放了,但是用户根本没有收到,大概率是被屏蔽了。假如你用模型选了100个能成交的用户,但是最终成交只有60个,那么40个在中间的哪一步给卡掉了,你都不知道,所以漏斗分析这种大框架还是必须的,不能忽略。

数学模型比较直接,行就是行,不行就是不行,但是业务或商业实践中有很多环节,需要一步一步去达成,这也一直有的调侃,理科直男的说法。也有点像在神经网络模型中,有很多隐藏层,每层之间必须要有激活函数,否则即使有多层,合并后其实也就是一层。这个激活函数,在不同层之间起到连接作用,连接不同的层。

业务侧分析与技术侧分析相辅相成,一个是大框架,一个是细节分析

这里只是小编的一些思考,有可能是错误的,毕竟小编还资历尚浅,需要不断学习,如果有错误理解的地方,欢迎大佬不吝赐教,公众号后台消息,小编会及时关注。

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

大家都知道现在大数据非常火爆,在大数据还没有出现时,用的都是“小数据”,这些“小数据”在分析时大部分用的都是Excel、SPSS等工具,直到现在把Excel运用的很熟练的人,仍然很受青睐。但是Python的到来,使处理大数据比较方便。那么之前在Excel、SPSS中的描述统计、假设检验在Python中怎么使用呢?下面将进行详细介绍,前提是已经掌握numpy、pandas这两个库,并且对统计知识有所了解

加载数据

这里引用的是GitHub上的一个CSV数据文件,insurance.csv 保险数据 ,可以下载下来参考练习 网址:https://github.com/stedy/Machine-Learning-with-R-datasets

>>> import numpy as np
>>> import pandas as pd
>>> data=pd.read_csv('F:/Machine-Learning datasets/insurance.csv')
>>> data
      age     sex     bmi  children smoker     region      charges
0      19  female  27.900         0    yes  southwest  16884.92400
1      18    male  33.770         1     no  southeast   1725.55230
2      28    male  33.000         3     no  southeast   4449.46200
3      33    male  22.705         0     no  northwest  21984.47061
4      32    male  28.880         0     no  northwest   3866.85520
...   ...     ...     ...       ...    ...        ...          ...
1333   50    male  30.970         3     no  northwest  10600.54830
1334   18  female  31.920         0     no  northeast   2205.98080
1335   18  female  36.850         0     no  southeast   1629.83350
1336   21  female  25.800         0     no  southwest   2007.94500
1337   61  female  29.070         0    yes  northwest  29141.36030

[1338 rows x 7 columns]
>>> data.isnull().sum()
age         0
sex         0
bmi         0
children    0
smoker      0
region      0
charges     0
dtype: int64
>>> data.dtypes
age           int64
sex          object
bmi         float64
children      int64
smoker       object
region       object
charges     float64
dtype: object

描述统计各指标

>>> data['age'].max()
64
>>> data['age'].min()
18
>>> data['age'].mean()
39.20702541106129
>>> data['age'].std()
14.049960379216172
>>> data['age'].median()
39.0

众数有时会有多个,这里是全部返回

>>> data['age'].mode()
0    18
dtype: int64

>>> d=pd.DataFrame([1,1,1,2,2,2,3,4,4,4,5,5,6],columns=['a'])
>>> d['a'].mode()
0    1
1    2
2    4
dtype: int64
>>> data['age'].quantile([0,0.25,0.5,0.75,1])
0.00    18.0
0.25    27.0
0.50    39.0
0.75    51.0
1.00    64.0
Name: age, dtype: float64
>>> data['region'].value_counts()
southeast    364
northwest    325
southwest    325
northeast    324
Name: region, dtype: int64
>>> data['age'].max()-data['age'].min()
46
>>> data['age'].quantile(0.75)-data['age'].quantile(0.25)
24.0
>>> data['age'].std()/data['age'].mean()
0.3583531326824994

假设检验

>>> data
      age     sex     bmi  children smoker     region      charges
0      19  female  27.900         0    yes  southwest  16884.92400
1      18    male  33.770         1     no  southeast   1725.55230
2      28    male  33.000         3     no  southeast   4449.46200
3      33    male  22.705         0     no  northwest  21984.47061
4      32    male  28.880         0     no  northwest   3866.85520
...   ...     ...     ...       ...    ...        ...          ...
1333   50    male  30.970         3     no  northwest  10600.54830
1334   18  female  31.920         0     no  northeast   2205.98080
1335   18  female  36.850         0     no  southeast   1629.83350
1336   21  female  25.800         0     no  southwest   2007.94500
1337   61  female  29.070         0    yes  northwest  29141.36030

[1338 rows x 7 columns]
>>> data['age'].mean()
39.20702541106129
>>> 
>>> import statsmodels.api as sm   #加载分析库
>>> t=sm.stats.DescrStatsW(data['age'])      #构造统计量对象
>>> t.ttest_mean(38)    #t检验,假设总体均值为38,返回t值、P值、自由度
(3.142457193279878, 0.0017121567548687802, 1337.0)
>>> t.ttest_mean(39)   #假设总体均值为39,p>0.05,接受原假设
(0.5389849179805168, 0.5899869939488361, 1337.0)
>>> t.ttest_mean(18)    #假设总体均值为18,p<0.05,小于0.05拒绝原假设
(55.21190269926711, 0.0, 1337.0)
>>> data.groupby('sex').mean()['charges']
sex
female    12569.578844
male      13956.751178
Name: charges, dtype: float64
>>> sex0=data[data['sex']=='female']['charges']
>>> sex1=data[data['sex']=='male']['charges']
>>> from scipy import stats
>>> leveneTestRes=stats.levene(sex0,sex1)  #方差齐性检验
>>> leveneTestRes   #p值<0.05,说明方差非齐性
LeveneResult(statistic=9.90925122305512, pvalue=0.0016808765833903443)
>>> stats.stats.ttest_ind(sex0,sex1,equal_var=False)   #双样本T检验
Ttest_indResult(statistic=-2.1008878232359565, pvalue=0.035841014956016645)

相关系数

>>> data[['age','charges']].corr(method='pearson')   #皮尔逊相关系数
              age   charges
age      1.000000  0.299008
charges  0.299008  1.000000
>>> data[['age','charges']].corr(method='spearman')   #斯皮尔曼等级相关系数
              age   charges
age      1.000000  0.534392
charges  0.534392  1.000000
>>> data[['age','charges']].corr(method='kendall')   #肯德尔相关系数
              age   charges
age      1.000000  0.475302
charges  0.475302  1.000000

以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

由于最近项目的需要,学习了分类算法--决策树,从逐步了解决策树,然后到手动在Excel里面模拟计算相关算法,最后到对scikit-learn库的运用,算是从理论到实践走了一遍,也发现scikit-learn库并不是完全对理论的实现,只是将部分算法实现出来,现总结分享出来供大家参考。
决策树大概的样子 决策树.png

决策树三个算法介绍

如果说对决策树比较熟悉的话,发展到现在主要有三个决策树算法,ID3、C4.5、CART这三个算法,其中ID3是利用信息增益,C4.5利用信息增益率,CATR利用基尼系数,C4.5是对ID3缺点的一个改进,但改进后还是有缺点,现在目前运用较多的是基尼系数,也就是CART这个算法,scikit-learn库里面的决策树默认的就是运用基尼系数来进行分枝。 三个算法.png

ID3--信息增益

信息增益也就是信息熵的差值,也就是通过一个节点分枝使信息熵减少了多少,信息熵是由香农提出的,相信大家都听说过这个大人物,那什么是信息熵呢,看了很多参考书,感觉解释的最通俗易懂的是吴军的《数学之美》里面对这个概念的阐述。 信息熵.png

信息熵的主要公式: image.png 信息增益是在条件熵下计算而来,类似于条件概率的意思 信息增益.png ID3算法过程: ID3.png

C4.5--信息增益率

说白了就是相对于信息增益多了一个比值,由于信息增益更倾向于分类比较多的属性,这个大家可能比较懵,详细解释一下,比如说一个变量(一个特征)A,是个分类变量,它下面包含A1,A2,A3三个不同的属性(水平),而另外一个变量B下面包含B1,B2,B3,......,B10共10个不同的属性,那么计算出来的信息增益,根据历史经验来说,变量B信息增益比较大,一般首先选取B来作为节点进行分枝。 而C4.5就是克服这个缺点应运而生,就是再用计算出来的这个信息增益除以这个变量本来的信息熵

信息增益率主要公式: 信息增益率.png

ID3与C4.5结合使用效果相对比较好

两者结合.png

scikit-learn库对信息增益的实现大概框架

scikit-learn库里面的决策树,没有具体说是信息增益,还是信息增益率,这个有兴趣可以仔细研究一下https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html#sklearn.tree.DecisionTreeClassifier

from sklearn import tree    # 导入决策树库

# 训练分类模型
model_tree = tree.DecisionTreeClassifier(criterion='entropy',random_state=0)  
# 建立决策树模型对象,需要指定criterion为entropy

model_tree.fit(X, y)   #对数据进行训练

feature_importance = model_tree.feature_importances_    # 指标重要性

模型效果可视化需要用到其他的库及软件,这里不再详述,可参考其他内容,主要点就是中文的显示问题,中文是可以显示的,相关文档也有介绍,大家也可以仔细研究

CART (Classification And Regression Tree) 分类回归树

CART.png

解释一下“纯”:在我的印象里这个字是形容女生的,怎么跑到这里来呢,呵呵,这里的纯是说,经过一个节点的分枝后,目标变量y里面都是一个类别的,就说是纯的,不纯,就是经过一个节点的分枝后,目标变量y里面还是比较乱,任然不能确定目标属于哪个类别

scikit-learn库对基尼系数的实现大概框架

from sklearn import tree    # 导入决策树库

# 训练分类模型
model_tree = tree.DecisionTreeClassifier(criterion='gini',random_state=0)  
# 建立决策树模型对象,需要指定criterion为gini,也可以不指定,默认的就是gini

model_tree.fit(X, y)   #对数据进行训练

feature_importance = model_tree.feature_importances_    # 指标重要性

模型可视化中文显示问题

需要把里面的字体替换为微软雅黑即可

names_list=['中文1','中文2','中文3',...]

tree.export_graphviz(model_tree,out_file=dot_data,feature_names=names_list, filled=True,rounded=True,special_characters=True)

graph4 = graphviz.Source(dot_data.getvalue().replace('helvetica','"Microsoft YaHei"'))
graph4.render('决策树-5组均分-2变量') #生成PDF文件

参考

  1. https://www.cnblogs.com/muzixi/p/6566803.html
  2. https://www.jianshu.com/p/35129fae39f6

以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

根据自己看的一些书籍及技术博客等内容,对机器学习算法进行一些总结,供大家参考 机器学习算法.png

统计里面的卡方检验

卡方检验主要是用来进行 分类变量(离散变量) 的关联性、相关性分析,其根本思想就是在于比较理论频数和实际频数的吻合程度或拟合优度问题。

在统计学里面最经典就是四方格检验,下面列举一个例子,让大家对卡方检验有一个真实的认识: 现在我们有一些样本,每个人是否喝牛奶,以及是否感冒,形式如下(只截图了一部分),现在我们想知道,是否喝牛奶是否感冒有影响,根据我们的常识判断,喝牛奶可以增强体质,有助于抵抗感冒,但一切结论都要有依据,才能使大家信服,于是这就用到统计学里面的卡方检验。

卡方检验原始数据

根据上面的数据,我们可以在Excel里面创建透视表,形成一个交叉表(列联表),统计交叉后的频数及占比情况,喝牛奶这一行的感冒率是用感冒的人数43除以喝牛奶总人数139,其他同理,如下所示:

交叉表数据

通过简单的统计我们得出喝牛奶和不喝牛奶的感冒率为30.94%和25.00%,两者的差别可能是抽样误差导致,也有可能是牛奶对感冒真的有影响。

先建立假设: H0(原假设):是否喝牛奶与是否感冒独立无关 H1(备择假设):是否喝牛奶与是否感冒是相关的,也就是喝牛奶会影响到感冒

为了确定真实原因,我们先假设喝牛奶对感冒是没有影响的,即喝牛奶和感冒是独立无关的,所以我们可以得出感冒的发病率实际是(43+28)/(43+28+96+84)= 28.29%

所以,理论上的数值如下表所示: 理论数值

即下表数据: 理论数值 卡方检验的公式为: 卡方公式

根据卡方检验公式我们可以得的卡方值为: 卡方 = (43 - 39.32)^2 / 39.32 + (28 - 31.68)^2 / 31.68 + (96 - 99.68)^2 / 99.68 + (84 - 80.32)^2 / 80.32 = 1.077

根据自由度和检验显著性水平0.05,通过查卡方表,可以得到对应的卡方值为3.84,即如果卡方大于3.84,则认为喝牛奶和感冒有95%的概率相关。显然1.077<3.84,没有达到卡方分布的临界值,原假设成立,即是否喝牛奶和是否感冒独立无关,其实通过公式也可以理解。

自由度等于= (行数 - 1) * (列数 - 1),对四格表,自由度= 1


通过上面的例子,大家应该对卡方检验有了深入理解,但是这个在机器学习sklearn库里面是怎么用的呢,以及用时我们应该怎么输入数据,上面例子我们是通过交叉表进行计算的,那么sklearn库又是怎么计算的呢?下面通过读官方的原始代码一步一步进行分析

官方原始代码

下面一句一句对官方原始代码进行解析

Y = LabelBinarizer().fit_transform(y)

if Y.shape[1] == 1:
        Y = np.append(1 - Y, Y, axis=1)
  • 对输入的y进行二值化处理,也就是对y里面的类别进行数值处理,形成一列数值,用1和0进行表示不同的两个类别,LabelBinarizer()对超过两个类别也能处理,类似和one-hot编码
  • 下面的if 是为了添加另一个类别,假如y里面只有两个类别的话,那么进行二值化处理后Y就只有1列,也就是只能表示y里面的一个类别,另外一个类别需要再添加上,新加的一列其实也就是对刚才二值化的数据取反
  • 通过上面的理解,在输入y时可以是字符型,也可以是数值型,在机器学习里面大家印象一般都是用数值型数据,但是在这一块可以用字符型,像咱们的汉字等

observed = safe_sparse_dot(Y.T, X)          # n_classes * n_features
  • observed意思是观察值,也就是对应卡方里面的真实值,那么这行意思就是求真实值,只不过是用矩阵的乘法来处理
  • 输入的X只能是数值型,这一块是用来进行矩阵乘法,那么也就是输入的分类变量必须处理成数值,需要用到one-hot,对每一个分类变量(离散变量)进行转换,这一块千万不能用 1 代表类别1, 2 代表类别2, 3 代表类别3 等,否则卡方检验就是错的,所选的特征同样也是错的,如果分类变量里面只有两个类别的话,可以直接用1,0
  • Y.T 与 X两个矩阵的乘法,结果为y里面每个类别的计数

feature_count = X.sum(axis=0).reshape(1, -1)
class_prob = Y.mean(axis=0).reshape(1, -1)
expected = np.dot(class_prob.T, feature_count)
  • 这三句代码是用来求理论数值,首先求出X矩阵每列求和,然后求出Y矩阵里面每个类别的占比(1的个数/总样本,其实也就是直接求平均,为理论占比),最后进行矩阵乘法,求出理论数值

return _chisquare(observed, expected)
  • 用计算卡方的函数传入真实值、理论值,计算出卡方值

下面利用上面的原始代码演示一遍

案例原始代码演示 可以看出这个数值和上面Excel模拟的数值完全一样,最后通过卡方计算函数传入真实值、理论值即可求出 “卡方值


但是,但是,但是 求出来的卡方值,并不是统计上的卡方值、P值

在sklearn.feature_selection中,用到chi2时,在fit时需要传入X、y,计算出来的卡方值是 单个特征变量对目标变量y的卡方值,下面从大家经常使用api的角度去展示上面是否喝牛奶与是否感冒的特征筛选过程 特征筛选基于卡方 chi2

sk.scores_
# array([0.59647887, 0.48061607])

sk.pvalues_
# array([0.43992466, 0.48814339])

可以看出计算出来两列值为0.59647887、0.48061607,这是X单个特征变量对目标变量y的卡方值,并不是上面Excel模拟计算的卡方值为1.077

而0.59647887+0.48061607=1.077,这个才是真正意义上的卡方检验的卡方值,那么对应的P值同样也不是统计意义上的P值

所以在sklearn.feature_selection.SelectKBest中基于卡方chi2,提取出来的比较好的特征变量,可以理解为在所有特征变量里面相对更好的特征,并不是统计里面分类变量与目标变量通过卡方检验得出的是否相关的结果,因此大家在进行特征筛选用到这个api时,要有真实的理解,感觉这点比较重要,分享出来供大家参考


以上是自己实践中遇到的一些点,分享出来供大家参考学习,欢迎关注本简书号

背景

在进行一些综合评估类项目时,需要给一些指标确定一个合理的权重,用来计算综合得分,这种综合评估类项目在实际的业务中有很多应用,比如:学生奖学金评定方法、广告效果综合评估、电视节目满意度综合评估、用户满意度综合评估等。计算权重的方法比较多,下面主要介绍利用熵值法来确定权重。

一些名词解释

  • 个案 一个个案,一条记录,也就是一个样本,在矩阵里面就是一行数据,不同地方叫法不一样
  • 属性 属性就是样本所拥有的特性,也就是特征,在矩阵里面就是一列数据

熵值法概念

熵值法原理: 熵的概念源于热力学,是对系统状态不确定性的一种度量。在信息论中,信息是系统有序程度的一种度量。而熵是系统无序程度的一种度量,两者绝对值相等,但符号相反。根据此性质,可以利用评价中各方案的固有信息,通过熵值法得到各个指标的信息熵,信息熵越小,信息的无序度越低,其信息的效用值越大,指标的权重越大

熵是不确定性的度量,如果用 $P_{i}$ 表示第 $i$ 个信息的不确定度(也就是出现的概率),则整个信息(设有 $n$ 个)的不确定度量如下所示: $$S=-K\sum_{i=1}^{n}P_{i}lnP_{i}$$ 这就是熵,其中 $K$ 为正常数,当各个信息发生的概率相等时,即 $$P_{i}=\frac{1}{n}$$, $S$取值最大,此时熵最大,也就是信息无序度最大,各个信息都发生可能性一样

熵值法步骤

    1. 可利用信息熵的概念确定权重,假设多属性决策矩阵如下: $$M=\begin{matrix} A_{1} \ A_{2} \ \vdots \ A_{n} \end{matrix} \left[\begin{matrix} x_{11} & x_{12} & \cdots & x_{1m}\ x_{21} & x_{22} & \cdots & x_{2m}\ \vdots & \vdots & \ddots & \vdots \ x_{n1} & x_{n2} & \cdots & x_{nm} \end{matrix}\right] $$

则用 $$P_{ij}=\frac{x_{ij}}{\sum_{i=1}^{ n}x_{ij}}$$ 表示第$j$个属性下第$i$个方案$A_{i}$的贡献度

    1. 可以用$E_j$来表示所有方案对属性$X_j$的总贡献度: $$E_j=-K\sum_{i=1}^{n}P_{ij}lnP_{ij}$$ 其中,常数$K=\frac{1}{ln(n)}$,这样,就能保证$0=<E_j<=1$,即$E_j$最大1。 由式中可以看出,当某个属性各个方案(样本)的贡献度趋于一致时,$E_j$趋于1。

那么各个方案(样本)的贡献度全相等时,就应该不考虑该属性在决策中的作用,也就是该属性的权重应该为0

    1. 这样可以看出属性的权重系数大小由各方案差异大小来决定,为此可定义$d_j$为第$j$属性的各方案贡献度的一致性程度 $$d_j=1-E_j$$
    1. 进行归一化后,各属性权重如下: $$W_j=\frac{d_j}{\sum_{j=1}^{m}d_j}$$当$d_j=0$时,第$j$属性可以剔除,其权重等于0
    1. 如果决策者事先已有一些经验的主观估计权重$\lambda_j$,则可借助上述的$W_j$来对$\lambda_j$进行修正 $$W_{j}^{*}=\frac{\lambda_j W_j}{\sum_{j=1}^{m}\lambda_j W_j}$$

熵值法最大的特点是直接利用决策矩阵所给出的信息计算权重,而没有引入决策者的主观判断,完全是依靠数据来决定

案例

购买汽车的一个决策矩阵,给出了四个方案供我们进行选择,每个方案中均有相同的六个属性,我们需要利用熵值法求出各属性的权重

车型油耗功率费用安全性维护性操作性
本田51.46357
奥迪9230759
桑塔纳81.811575
别克122.518755
计算步骤
    1. 求第$j$个属性下第$i$个方案$A_i$的贡献度,公式为: $$P_{ij}=\frac{x_{ij}}{\sum_{i=1}^{ n}x_{ij}}$$在excel中的话,先求出各列的和,然后用每行的数值比上列和,形成新的矩阵
车型油耗功率费用安全性维护性操作性
本田51.46357
奥迪9230759
桑塔纳81.811575
别克122.518755
总计347.765222226

$P$矩阵:

车型油耗功率费用安全性维护性操作性
本田5/341.4/7.76/653/225/227/26
奥迪9/342/7.730/657/225/229/26
桑塔纳8/341.8/7.711/655/227/225/26
别克12/342.5/7.718/657/225/225/26

    1. 求出所有方案对属性$X_j$的贡献总量,公式为: $$E_j=-K\sum_{i=1}^{n}P_{ij}lnP_{ij}$$ 在excel操作中,将刚才生成的矩阵每个元素变成每个元素与该元素自然对数的乘积

只列出油耗计算过程,其他属性同理

车型油耗
本田5/34 * ln(5/34)
奥迪9/34 * ln(9/34)
桑塔纳8/34 * ln(8/34)
别克12/34 * ln(12/34)
总计5/34* ln(5/34) + 9/34* ln(9/34) + 8/34* ln(8/34) + 12/34* ln(12/34)

求出常数$k$,$k$为$1/ln(方案数)$,本例中有4个方案,所以求得$k$为$1/ln(4)$,再求$k$与新矩阵每一列和的乘积,这样获得的 6 个积为所有方案对属性$x_j$的贡献度

车型油耗
本田5/34 * ln(5/34)
奥迪9/34 * ln(9/34)
桑塔纳8/34 * ln(8/34)
别克12/34 * ln(12/34)
总计5/34* ln(5/34) + 9/34* ln(9/34) + 8/34* ln(8/34) + 12/34* ln(12/34)
$E_j$1/ln(4) * [ 5/34* ln(5/34) + 9/34* ln(9/34) + 8/34* ln(8/34) + 12/34* ln(12/34) ]

至此所有的$E_j$就求出来了

    1. $d_j$为第$j$属性下各方案贡献度的一致性程度,公式为:

$$d_j=1-E_j$$利用上面求得的$E_j$,可以得到$d_j$

车型油耗
本田5/34 * ln(5/34)
奥迪9/34 * ln(9/34)
桑塔纳8/34 * ln(8/34)
别克12/34 * ln(12/34)
总计5/34* ln(5/34) + 9/34* ln(9/34) + 8/34* ln(8/34) + 12/34* ln(12/34)
$E_j$1/ln(4) * [ 5/34* ln(5/34) + 9/34* ln(9/34) + 8/34* ln(8/34) + 12/34* ln(12/34) ]
$d_j$1 - 1/ln(4) * [5/34* ln(5/34) + 9/34* ln(9/34) + 8/34* ln(8/34) + 12/34* ln(12/34) ]
    1. 利用下面公式进行归一化后,即可求得各属性的权重: $$W_j=\frac{d_j}{\sum_{j=1}^{m}d_j}$$

经过计算后各属性的权重为:

车型油耗功率费用安全性维护性操作性
权重0.140.070.490.160.040.10

所以在购买汽车时,据所提供信息,利用熵值法计算得出的权重为油耗占 14%,功率占 7%,费用占 49%,安全性占 16%,维护性占 4%,操作性占 10%。故我们在进行购买决策时,更多是考虑车型的价格和安全性等重要因素,这是从权重角度考虑的。

利用Python实现熵值法:

代码如下:

import pandas as pd
import numpy as np
import math
from numpy import array

# 定义熵值法函数  熵值法计算变量的权重
def cal_weight(df):
    #求k
    rows = df.index.size  # 行
    cols = df.columns.size  # 列
    k = 1.0 / math.log(rows)

    # 矩阵计算、信息熵
    x = array(df)
    lnf = [[None] * cols for i in range(rows)]
    lnf = array(lnf)
    for i in range(0, rows):
        for j in range(0, cols):
            if x[i][j] == 0:
                lnfij = 0.0
            else:
                p = x[i][j] / np.sum(x, axis=0)[j]
                lnfij = math.log(p) * p * (-k)
            lnf[i][j] = lnfij
    lnf = pd.DataFrame(lnf)
    E = lnf

    # 计算一致性程度
    d = 1 - E.sum(axis=0)

    # 计算各指标的权重
    w = [[None] * 1 for i in range(cols)]
    for j in range(0, cols):
        wj = d[j] / sum(d)
        w[j] = wj

    w = pd.DataFrame(w)
    w.index = df.columns
    w.columns = ['权重']
    
    return w

以上是自己实践中遇到的一些点,分享出来供大家参考学习,欢迎关注本简书号

背景

在数据分析中总会碰见 阈(yu)值 这个问题,有人可能就是拍脑袋,根据自己的经验随便确定一个值,而且效果还不错。但既然是在分析数据,那就要确定一个合适的值来作为阈值,这样比较有说服力,更 make sense。

工作中要确定阈值的场景有很多:

案例1: 声纹相似度

人的声纹信息可以说是自己的另一个身份ID,可以根据声纹信息确定是否是同一个人,比如:微信app登陆时让用户读一个6位数,验证是否是该用户的声音,那微信是怎么来确定这个声音就是你呢?这里简化一下问题,假如微信服务器存的是根据你的声音提取出来的一个特征向量,那就是从你这次读的语音提取的特征向量与微信服务器存的特征向量的相似度问题,由于每次说话的音调不同,两个向量可能不完全相同,所以两个向量达到80%相似,还是90%相似,还是99%相似,微信让你登录呢,这里的相似度就是一个阈值。

案例2: NLP训练语句长度

在进行NLP模型训练时,提供的语料里面的每句话长度均不一样,那么在训练时应该怎么选取这个长度呢?如果用最大的长度,那么内存可能会暴增,如果用的语句长度太短,那么可能会遗漏一部分有用的信息,所以这里的句子长度就是一个阈值。

数据分布直方图

可以先利用数据分布直方图来确定数据的一个大概分布情况,

  • 如果数据是服从标准正态分布,那么阈值可以定义为 x ± 3sigma 或者 x ± 6sigma; 正态分布3sigma

  • 如果数据是一个偏分布,那么可以取一个分位数值90%或者95%,这个要视具体情况而定

下面列举一个偏分布的情况: 偏分布

import numpy as np

data=np.round(np.random.randn(1000)*100+100,0)

data=np.where(data<0,data*-1,data)   #构造数据

len(data)

%matplotlib inline
from matplotlib import pyplot as plt
plt.hist(data,bins=20)   #直方图
plt.show()

import pandas as pd
pd.DataFrame(data).quantile([0,0.25,0.5,0.75,0.8,0.9,0.95,1])    #分位数

上面的模拟数据是一个偏分布情况,那么可以用一个分位数来确定,如果阈值取300,那么就可以包括最少95%的情况,如果想要更精确一些,那么阈值可以取400等更大的一个值

以上是在项目中的一个小分析

往期相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

“人以类聚,物以群分”,在大千世界中总有那么一些人,性格爱好、行为习惯比较相近,我们就会把他们归为一类人,这就是我们人脑自动进行的一个聚类(归类)。 在数据分析中,我们也经常拿数据来进行K-Means聚类,用来进行一些分析,K-Means聚类可能大家都会,但是如何科学的决策聚类簇数,这一直是聚类的一大难题。今天就来分享一下,自己工作中在确定聚类簇数时要到的方法。

方法

可以确定聚类簇数的方法
  • Adjusted Rand index 调整兰德系数
  • Mutual Information based scores 互信息
  • Homogeneity, completeness and V-measure 同质性、完整性、两者的调和平均
  • Silhouette Coefficient 轮廓系数
  • Calinski-Harabaz Index
  • SSE 簇里误差平方和
自己使用的方法,手肘法则SSE 和 轮廓系数 相结合
  • SSE 簇里误差平方和

SSE利用计算误方差和,来实现对不同K值的选取后,每个K值对应簇内的点到中心点的距离误差平方和,理论上SSE的值越小,代表聚类效果越好,通过数据测试,SSE的值会逐渐趋向一个最小值。

  • Silhouette Coefficient 轮廓系数

好的聚类:内密外疏,同一个聚类内部的样本要足够密集,不同聚类之间样本要足够疏远。

轮廓系数计算规则: 针对样本空间中的一个特定样本,计算它与所在簇中其它样本的平均距离a,以及该样本与距离最近的另一个聚类中所有样本的平均距离b,该样本的轮廓系数为 单个样本点的轮廓系数

详细分解

对于其中的一个点 i 来说: 计算 a(i) = average(i向量到所有它属于的簇中其它点的距离) 计算 b(i) = min (i向量到各个非本身所在簇的所有点的平均距离) 分解步骤

然后将整个样本空间中所有样本的轮廓系数取算数平均值,作为聚类划分的性能指标轮廓系数

轮廓系数的区间为:[-1, 1]。 -1代表分类效果差,1代表分类效果好。0代表聚类重叠,没有很好的划分聚类

应用、代码

以鸢尾花数据集为案例

from sklearn import datasets
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
import sklearn.metrics as sm
import pandas as pd

%matplotlib inline
import matplotlib.pyplot as plt

#中文乱码的处理  显示负号
plt.rcParams['font.sans-serif']=['Microsoft YaHei']
plt.rcParams['axes.unicode_minus']=False

# 载入鸢尾花数据集
iris = datasets.load_iris()

data=pd.DataFrame(iris['data'],columns=iris['feature_names'])

std = StandardScaler()
data_std=std.fit_transform(data)

SSE = []
k_SSE = []
#簇的数量
for n_clusters in range(1,11):
    cls = KMeans(n_clusters).fit(data_std)
    score = cls.inertia_
    SSE.append(score)
    k_SSE.append(n_clusters)

silhouette_score = []
k_sil = []
#簇的数量
for n_clusters in range(2,11):
    cls = KMeans(n_clusters).fit(data_std)
    pred_y=cls.labels_
    
    #轮廓系数
    score =sm.silhouette_score(data_std, pred_y)
     
    silhouette_score.append(score)
    k_sil.append(n_clusters)

fig, ax1 = plt.subplots(figsize=(10, 6))

ax1.scatter(k_SSE, SSE)
ax1.plot(k_SSE, SSE)
ax1.set_xlabel("k",fontdict={'fontsize':15})
ax1.set_ylabel("SSE",fontdict={'fontsize':15})
ax1.set_xticks(range(11))
for x,y in zip(k_SSE,SSE):
    plt.text(x, y,x)

ax2 = ax1.twinx()
ax2.scatter(k, silhouette_score,marker='^',c='red')
ax2.plot(k, silhouette_score,c='red')
ax2.set_ylabel("silhouette_score",fontdict={'fontsize':15},)
for x,y in zip(k_sil,silhouette_score):
    plt.text(x, y,x)

plt.show()

iris 聚类 结合SSE与轮廓系数,当k=3时比较适合,可以确定聚类的簇数为3

而鸢尾花数据集里面真实的分类就是3类

iris['target_names']
#array(['setosa', 'versicolor', 'virginica'], dtype='<U10')

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号,不定期分享干货

背景

在对文本进行处理分析时,大家第一印象就是对句子进行分词,统计词频,看哪些词语出现的词频较高,重点关注这些高频词即可,文章可能就是围绕着这些词展开的。中文的分词工具,大家耳熟能详的可能就是结巴分词,但是结巴分词最近也没有怎么更新,随着技术的不断迭代有一些更优秀的分词工具诞生,比如:LAC(百度)、THULAC(清华大学)、LTP(哈工大)、FoolNLTK等

这里主要介绍一下百度的LAC,现在已更新到v2.1,GitHub地址:https://github.com/baidu/lac,使用起来速度与效果还可以,足以应对简单的分词任务

LAC介绍

LAC全称 Lexical Analysis of Chinese,是百度自然语言处理部研发的一款联合的词法分析工具,实现中文分词、词性标注、专名识别等功能。该工具具有以下特点与优势:

  • 效果好:通过深度学习模型联合学习分词、词性标注、专名识别任务,词语重要性,整体效果F1值超过0.91,词性标注F1值超过0.94,专名识别F1值超过0.85,效果业内领先。
  • 效率高:精简模型参数,结合Paddle预测库的性能优化,CPU单线程性能达800QPS,效率业内领先。
  • 可定制:实现简单可控的干预机制,精准匹配用户词典对模型进行干预。词典支持长片段形式,使得干预更为精准。
  • 调用便捷:支持一键安装,同时提供了Python、Java和C++调用接口与调用示例,实现快速调用和集成。
  • 支持移动端: 定制超轻量级模型,体积仅为2M,主流千元手机单线程性能达200QPS,满足大多数移动端应用的需求,同等体积量级效果业内领先。

功能看着很强大,但是这里只用到中文分词功能,下面介绍一下使用的demo,

通过 pip install lac 进行安装即可

使用教程

直接使用lac分词

加载LAC 后,通过其自带的模型进行分词,结果为一个列表

from LAC import LAC

# 装载分词模型
lac = LAC(mode='seg')

text='我是一名北漂的打工人、干饭人'
lac.run(text)

text_list=['我是一名北漂的打工人、干饭人','5月15日,航天科研人员在北京航天飞行控制中心指挥大厅庆祝我国首次火星探测任务着陆火星成功']
lac.run(text_list)

直接使用lac分词

加载自定义字典

从上面可以看出“打工人”可以正确分词,但是“干饭人”不能正确的切分,可以通过加载自定义字典来进行处理这种情况 自定义字典

from LAC import LAC

#装载分词模型
lac = LAC(mode='seg')

#加载自定义字典
lac.load_customization('自定义字典.txt', sep=None)

text='我是一名北漂的打工人、干饭人'
lac.run(text)

text_list=['我是一名北漂的打工人、干饭人',
           '5月15日,航天科研人员在北京航天飞行控制中心指挥大厅庆祝我国首次火星探测任务着陆火星成功']
lac.run(text_list)

加载自定义字典

从上面的输出结果可以看出,已经正确分词

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

小编是在2019年开始学习机器学习,在查找资料时知道的OpenAI公司,当时该公司才成立没多久(创建于2018年),他的一些技术让小编感觉很厉害,便对他有了深刻印象。这短短过去4年时间,该公司便使人工智能又上了一个台阶,让大家都称赞不绝

GPT发展历程

官方的模型介绍文档:

从文档可以看出,开放的api是GPT-3,所以在调动api时,使用的模型是GPT-3,但是OpenAI官网的聊天,使用的是GPT-3.5,所以同样的问题,回答可能会不一样, 发展历程

GPT-3(Generative Pre-trained Transformer 3,简称GPT-3)指的是生成型预训练变换模型第3版,是一个自回归语言模型,目的是为了使用深度学习生成人类可以理解的自然语言

GPT-3.5 与GPT-3 最大的差别在于GPT-3 主要扮演一个搜集资料的角色,较单纯的使用网路上的资料进行训练。而GPT-3.5 则是由GPT-3 微调出来的版本,而其中GPT-3.5 使用与GPT-3 不同的训练方式,所产生出来不同的模型,比起GPT-3 来的更强大

最近特别火的ChatGPT就是是建立GPT-3.5 之上,且更加上使用更完整的 人类反馈强化学习(RLHF)去训练。(大致上可以想成GPT-3 → GPT-3.5 → ChatGPT这样)

也因此ChatGPT 除了能够准确理解问题,更能够将对话一路记住和按此调整内容,其中包括承认错误、纠正错处和拒绝不当要求等等较为复杂的互动内容,更符合道德要求的训练方式,达到更接近真人的效果,这也是GPT-3 所没有的。

注册要点

因为官方限制了在中国地区使用,并且必须得用国外手机号验证,难点就是得有一个国外的手机号,可以使用接码平台,懂的话可以试试注册一个玩一下,如果对翻墙、机场、接码平台不了解的话,小编不建议大家折腾半天来注册一个官方账号

现在国内有一些网站、微信群基于api接口,开发出来供大家尝鲜使用,但基于的是不是OpenAI的api接口,这个有待验证(国内的开发大家都知道,喜欢骗人,要么是广告,要么调用就不是真正的OpenAI接口),所以大家在试玩的时候,得注意安全

基于官方网页版试玩

测试-1

测试-2

基于apikey试玩

apikey

import openai
openai.api_key = 'sk-xxxx'  #注册的api key

response = openai.Completion.create(
  model='text-davinci-003',  #GPT-3 模型
  prompt='给我一些全球的新闻网址',
  temperature=0.5,
  max_tokens=2048,
  top_p=1.0,
  frequency_penalty=0.5,
  presence_penalty=0.0,
)

print(response.choices[0].text)

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景介绍

自然语言处理(NLP)在深度学习领域是一大分支(其他:CV、语音),经过这些年的发展NLP发展已经很成熟,同时在工业界也慢慢开始普及,谷歌开放的Bert是NLP前进的又一里程碑。本篇文章结合Bert与Lstm,对文本数据进行二分类的研究。

需要的第三方库

  • pandas
  • numpy
  • torch
  • transformers
  • sklearn

以上这些库需要读者对机器学习、深度学习有一定了解

数据及预训练Bert

完整过程

  • 数据预处理
import pandas as pd
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import TensorDataset, DataLoader

np.random.seed(2020)
torch.manual_seed(2020)
USE_CUDA = torch.cuda.is_available()
if USE_CUDA:
    torch.cuda.manual_seed(2020)

data=pd.read_csv('./dianping.csv',encoding='utf-8')

#剔除标点符号,\xa0 空格
def pretreatment(comments):
    result_comments=[]
    punctuation='。,?!:%&~()、;“”&|,.?!:%&~();""'
    for comment in comments:
        comment= ''.join([c for c in comment if c not in punctuation])
        comment= ''.join(comment.split())   #\xa0
        result_comments.append(comment)
    
    return result_comments

result_comments=pretreatment(list(data['comment'].values))

len(result_comments)

2000

result_comments[:1]

['口味不知道是我口高了还是这家真不怎么样我感觉口味确实很一般很一般上菜相当快我敢说菜都是提前做好的几乎都不热菜品酸汤肥牛干辣干辣的还有一股泡椒味着实受不了环境室内整体装修确实不错但是大厅人多太乱服务一般吧说不上好但是也不差价格一般大众价格都能接受人太多了排队很厉害以后不排队也许还会来比如早去路过排队就不值了票据六日没票告我周一到周五可能有票相当不正规在这一点同等价位远不如外婆家']

  • 利用transformers 先进行分字编码
from transformers import BertTokenizer,BertModel

tokenizer = BertTokenizer.from_pretrained("./chinese-bert_chinese_wwm_pytorch/data")

result_comments_id=tokenizer(result_comments,padding=True,truncation=True,max_length=200,return_tensors='pt')

result_comments_id

{'input_ids': tensor([[ 101, 1366, 1456, ..., 0, 0, 0], [ 101, 5831, 1501, ..., 0, 0, 0], [ 101, 6432, 4696, ..., 0, 0, 0], ..., [ 101, 7566, 4408, ..., 0, 0, 0], [ 101, 2207, 6444, ..., 0, 0, 0], [ 101, 2523, 679, ..., 0, 0, 0]]), 'token_type_ids': tensor([[0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], ..., [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, ..., 0, 0, 0], [1, 1, 1, ..., 0, 0, 0], [1, 1, 1, ..., 0, 0, 0], ..., [1, 1, 1, ..., 0, 0, 0], [1, 1, 1, ..., 0, 0, 0], [1, 1, 1, ..., 0, 0, 0]])}

result_comments_id['input_ids'].shape

torch.Size([2000, 200])

  • 分割数据集
from sklearn.model_selection import train_test_split
X=result_comments_id['input_ids']
y=torch.from_numpy(data['sentiment'].values).float()

X_train,X_test, y_train, y_test =train_test_split(X,y,test_size=0.3,shuffle=True,stratify=y,random_state=2020)

len(X_train),len(X_test)

(1400, 600)

X_valid,X_test,y_valid,y_test=train_test_split(X_test,y_test,test_size=0.5,shuffle=True,stratify=y_test,random_state=2020)

len(X_valid),len(X_test)

(300, 300)

X_train.shape

torch.Size([1400, 200])

y_train.shape

torch.Size([1400])

y_train[:1]

tensor([1.])

  • 数据生成器
# create Tensor datasets
train_data = TensorDataset(X_train, y_train)
valid_data = TensorDataset(X_valid, y_valid)
test_data = TensorDataset(X_test,y_test)

# dataloaders
batch_size = 32

# make sure the SHUFFLE your training data
train_loader = DataLoader(train_data, shuffle=True, batch_size=batch_size,drop_last=True)
valid_loader = DataLoader(valid_data, shuffle=True, batch_size=batch_size,drop_last=True)
test_loader = DataLoader(test_data, shuffle=True, batch_size=batch_size,drop_last=True)
  • 建立模型
if(USE_CUDA):
    print('Training on GPU.')
else:
    print('No GPU available, training on CPU.')

Training on GPU.

class bert_lstm(nn.Module):
    def __init__(self, hidden_dim,output_size,n_layers,bidirectional=True, drop_prob=0.5):
        super(bert_lstm, self).__init__()
 
        self.output_size = output_size
        self.n_layers = n_layers
        self.hidden_dim = hidden_dim
        self.bidirectional = bidirectional
        
        #Bert ----------------重点,bert模型需要嵌入到自定义模型里面
        self.bert=BertModel.from_pretrained("../chinese-bert_chinese_wwm_pytorch/data")
        for param in self.bert.parameters():
            param.requires_grad = True
        
        # LSTM layers
        self.lstm = nn.LSTM(768, hidden_dim, n_layers, batch_first=True,bidirectional=bidirectional)
        
        # dropout layer
        self.dropout = nn.Dropout(drop_prob)
        
        # linear and sigmoid layers
        if bidirectional:
            self.fc = nn.Linear(hidden_dim*2, output_size)
        else:
            self.fc = nn.Linear(hidden_dim, output_size)
          
        #self.sig = nn.Sigmoid()
 
    def forward(self, x, hidden):
        batch_size = x.size(0)
        #生成bert字向量
        x=self.bert(x)[0]     #bert 字向量
        
        # lstm_out
        #x = x.float()
        lstm_out, (hidden_last,cn_last) = self.lstm(x, hidden)
        #print(lstm_out.shape)   #[32,100,768]
        #print(hidden_last.shape)   #[4, 32, 384]
        #print(cn_last.shape)    #[4, 32, 384]
        
        #修改 双向的需要单独处理
        if self.bidirectional:
            #正向最后一层,最后一个时刻
            hidden_last_L=hidden_last[-2]
            #print(hidden_last_L.shape)  #[32, 384]
            #反向最后一层,最后一个时刻
            hidden_last_R=hidden_last[-1]
            #print(hidden_last_R.shape)   #[32, 384]
            #进行拼接
            hidden_last_out=torch.cat([hidden_last_L,hidden_last_R],dim=-1)
            #print(hidden_last_out.shape,'hidden_last_out')   #[32, 768]
        else:
            hidden_last_out=hidden_last[-1]   #[32, 384]
            
            
        # dropout and fully-connected layer
        out = self.dropout(hidden_last_out)
        #print(out.shape)    #[32,768]
        out = self.fc(out)
        
        return out
    
    def init_hidden(self, batch_size):
        weight = next(self.parameters()).data
        
        number = 1
        if self.bidirectional:
            number = 2
        
        if (USE_CUDA):
            hidden = (weight.new(self.n_layers*number, batch_size, self.hidden_dim).zero_().float().cuda(),
                      weight.new(self.n_layers*number, batch_size, self.hidden_dim).zero_().float().cuda()
                     )
        else:
            hidden = (weight.new(self.n_layers*number, batch_size, self.hidden_dim).zero_().float(),
                      weight.new(self.n_layers*number, batch_size, self.hidden_dim).zero_().float()
                     )
        
        return hidden
output_size = 1
hidden_dim = 384   #768/2
n_layers = 2
bidirectional = True  #这里为True,为双向LSTM

net = bert_lstm(hidden_dim, output_size,n_layers, bidirectional)

#print(net)
  • 训练模型
# loss and optimization functions
lr=2e-5
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(net.parameters(), lr=lr)

# training params
epochs = 10
# batch_size=50
print_every = 7
clip=5 # gradient clipping
 
# move model to GPU, if available
if(USE_CUDA):
    net.cuda()
net.train()
# train for some number of epochs
for e in range(epochs):
    # initialize hidden state
    h = net.init_hidden(batch_size)
    counter = 0
 
    # batch loop
    for inputs, labels in train_loader:
        counter += 1
        
        if(USE_CUDA):
            inputs, labels = inputs.cuda(), labels.cuda()
        h = tuple([each.data for each in h])
        net.zero_grad()
        output= net(inputs, h)
        loss = criterion(output.squeeze(), labels.float())
        loss.backward()
        optimizer.step()
 
        # loss stats
        if counter % print_every == 0:
            net.eval()
            with torch.no_grad():
                val_h = net.init_hidden(batch_size)
                val_losses = []
                for inputs, labels in valid_loader:
                    val_h = tuple([each.data for each in val_h])

                    if(USE_CUDA):
                        inputs, labels = inputs.cuda(), labels.cuda()

                    output = net(inputs, val_h)
                    val_loss = criterion(output.squeeze(), labels.float())

                    val_losses.append(val_loss.item())
 
            net.train()
            print("Epoch: {}/{}...".format(e+1, epochs),
                  "Step: {}...".format(counter),
                  "Loss: {:.6f}...".format(loss.item()),
                  "Val Loss: {:.6f}".format(np.mean(val_losses)))

Epoch: 1/10... Step: 7... Loss: 0.679703... Val Loss: 0.685275
Epoch: 1/10... Step: 14... Loss: 0.713852... Val Loss: 0.674887
.............
Epoch: 10/10... Step: 35... Loss: 0.078265... Val Loss: 0.370415
Epoch: 10/10... Step: 42... Loss: 0.171208... Val Loss: 0.323075

  • 测试
test_losses = [] # track loss
num_correct = 0
 
# init hidden state
h = net.init_hidden(batch_size)
 
net.eval()
# iterate over test data
for inputs, labels in test_loader:
    h = tuple([each.data for each in h])
    if(USE_CUDA):
        inputs, labels = inputs.cuda(), labels.cuda()
    output = net(inputs, h)
    test_loss = criterion(output.squeeze(), labels.float())
    test_losses.append(test_loss.item())
    output=torch.nn.Softmax(dim=1)(output)
    pred=torch.max(output, 1)[1]

    # compare predictions to true label
    correct_tensor = pred.eq(labels.float().view_as(pred))
    correct = np.squeeze(correct_tensor.numpy()) if not USE_CUDA else np.squeeze(correct_tensor.cpu().numpy())
    num_correct += np.sum(correct)

print("Test loss: {:.3f}".format(np.mean(test_losses)))
 
# accuracy over all test data
test_acc = num_correct/len(test_loader.dataset)
print("Test accuracy: {:.3f}".format(test_acc))

Test loss: 0.442 Test accuracy: 0.827

  • 直接用训练的模型推断
def predict(net, test_comments):
    result_comments=pretreatment(test_comments)   #预处理去掉标点符号
    
    #转换为字id
    tokenizer = BertTokenizer.from_pretrained("./chinese-bert_chinese_wwm_pytorch/data")
    result_comments_id=tokenizer(result_comments,padding=True,truncation=True,max_length=120,return_tensors='pt')
    tokenizer_id=result_comments_id['input_ids']
    inputs=tokenizer_id
    batch_size = inputs.size(0)
    
    # initialize hidden state
    h = net.init_hidden(batch_size)
    
    if(USE_CUDA):
        inputs = inputs.cuda()
    
    net.eval()
    with torch.no_grad():
        # get the output from the model
        output = net(inputs, h)
        output=torch.nn.Softmax(dim=1)(output)
        pred=torch.max(output, 1)[1]
        # printing output value, before rounding
        print('预测概率为: {:.6f}'.format(output.item()))
        if(pred.item()==1):
            print("预测结果为:正向")
        else:
            print("预测结果为:负向")
comment1 = ['菜品一般,不好吃!!']
predict(net, comment1)  

预测概率为: 0.015379 预测结果为:负向

comment2 = ['环境不错']
predict(net, comment2)

预测概率为: 0.972344 预测结果为:正向

comment3 = ['服务员还可以,就是菜有点不好吃']
predict(net, comment3)

预测概率为: 0.581665 预测结果为:正向

comment4 = ['服务员还可以,就是菜不好吃']
predict(net, comment4)

预测概率为: 0.353724 预测结果为:负向

  • 保存模型
# 保存
torch.save(net.state_dict(), './大众点评二分类_parameters.pth')
  • 加载保存的模型,进行推断
output_size = 1
hidden_dim = 384   #768/2
n_layers = 2
bidirectional = True  #这里为True,为双向LSTM

net = bert_lstm(hidden_dim, output_size,n_layers, bidirectional)
net.load_state_dict(torch.load('./大众点评二分类_parameters.pth'))
# move model to GPU, if available
if(USE_CUDA):
    net.cuda()
comment1 = ['菜品一般,不好吃!!']
predict(net, comment1)

预测概率为: 0.015379 预测结果为:负向


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

在平时业务运营分析中经常会提取数据,也就是大家俗称的Sql Boy,表哥表姐,各大公司数据中台现在大部分用的都是基于Hadoop的分布式系统基础架构,用的比较多的有Hive数据仓库工具,数据分析师在数据查询时用的就是HQL,语法与Mysql有所不同,基本每天都会写大量的HQL语句,但你有试过哪些风格的写法呢?哪种风格的查询语句更容易理解呢?可能不同的人有不同的看法,下面展示具体的风格代码样式,看看你喜欢哪种

Hadoop是一个由Apache基金会所开发的分布式系统基础架构。用户可以在不了解分布式底层细节的情况下,开发分布式程序。充分利用集群的威力进行高速运算和存储。Hadoop实现了一个分布式文件系统( Distributed File System),其中一个组件是HDFS(Hadoop Distributed File System)

hive是基于Hadoop的一个数据仓库工具,用来进行数据提取、转化、加载,这是一种可以存储、查询和分析存储在Hadoop中的大规模数据的机制。hive数据仓库工具能将结构化的数据文件映射为一张数据库表,并提供SQL查询功能,能将SQL语句转变成MapReduce任务来执行。

风格一

这种风格大家都比较常用,从结果向源头倒着推,直接多层嵌套,一层一层往里面写,业务逻辑复杂的话有可能写很多层,达到几百行之多,目前很多公司在有数仓的支持下,基本嵌套的层数会比较少

select *
from
(
	(select *
	from a_temp
	where xxxx
    group by xxxx) as a
	left join 
	(select *
	from b_temp
	where xxxx) as b 
	on a.id=b.id
) temp
where xxxx
group by xxxx
order by xxxx

风格二

这种风格是利用 with 语句,从源头向结果正向推,可以把 with 语句理解为建立了一个临时视图/表一样,后面的表引用前面的表,逻辑是正向推进

with a as(select *
		from a_temp
		where xxxx 
		group by xxxx),
	 b as(select *
		from b_temp
		where xxxx)
select *
from a left join b on a.id=b.id
where xxxx 
group by xxxx
order by xxxx

两种风格的区别

  • 风格一:用的最多,从结果向源头倒着推
  • 风格二:容易理解,从源头向结果正向推

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

最近对用户的行为数据进行统计分析时,需要列出不同用户的具体详情,方便进行观察,在hive中虽然有排序函数,但是处理键值对数据时,不能根据值进行排序,需要巧妙借助中间过程来处理,总结出来与大家进行分享,也方便后面自己查找使用

预想效果

键值对排序

创建测试数据

--创建临时表
use test;
create table tmp_datashare
(id string comment '用户id',
click string comment '点击位置',
cnt int comment '点击次数')
COMMENT '用户点击行为统计'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n';

--加载数据
load data local inpath '/tmp/datashare.txt' overwrite into table tmp_datashare;

测试数据:

测试数据

数据处理过程

数据处理具体步骤:

  • 运用窗口函数进行降序排列增加一个添加辅助列
  • 对数据进行拼接并补全数字,比如:id_1中 首页:20,降序序号:2,需要转换为 00002:首页:20
  • 然后再进行分组聚合运用sort_array进行排序,并进行拼接
  • 最后再进行替换

具体代码如下:

with a as (select id,click,cnt,
			row_number() over(partition by id order by cnt desc) as rn
		from tmp_datashare
			),
	b as (select id,click,cnt,
			concat(lpad(rn, 5, '0'), '#', click, ':',cnt) as click_cnt_temp_1
		from a
		),
	c as (select id,
			concat_ws(';',
				sort_array(collect_list(click_cnt_temp_1))
				) as click_cnt_temp_2
		from b
		group by id
		)
select id,regexp_replace(click_cnt_temp_2,'\\d+#','') as click_cnt
from c

结果数据:

结果数据

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

业务场景:统计每个小时视频同时在线观看人数,因后台的业务数据是汇总之后的,只有开始时间、结束时间,没有每小时的详细日志数据,无法直接进行统计,所以需要对每条业务数据进行拆分,来统计每个小时的同时数

当然,如果有详细的日志数据也是直接可以统计的,但是正常情况下,日志数据会非常大,如果每个用户每30秒会产生一条数据,那么每天会产生大量的数据,如此大量的数据,很难长期保存

模拟数据与需求效果展示

对每行数据,按每小时进行拆分,结果如下所示:

模拟数据与需求效果展示

创建测试数据

--创建临时表
create table test.tmp_datashare
(user_id string comment '用户id',
start_time string comment '开始时间',
end_time string comment '结束时间')
comment '业务数据'
row format delimited fields terminated by '\t' 
lines terminated by '\n';

--加载数据
load data local inpath '/tmp/datashare.txt' 
overwrite into table test.tmp_datashare;

测试数据:

测试数据

数据处理过程

  • 数据处理的要点:

需要借助以下两个函数生成连续序列,然后用开始时间与该序列进行加和,生成相应的结果 space:空格字符串函数,语法: space(int n),返回长度为n的空字符串 posexplode:炸裂函数,会同时返回两个值,数据的下标索引、数据的值

  • 具体代码如下:

左右滑动查看代码

set hive.cli.print.header=true;

with a as(select user_id,start_time,end_time
		from test.tmp_datashare
		),
	b as(select user_id,start_time,end_time,pos
		from a 
		lateral view posexplode(
			split(
				space(
					cast((unix_timestamp(substr(end_time,1,13),'yyyy-MM-dd HH')-
							unix_timestamp(substr(start_time,1,13),'yyyy-MM-dd HH'))/3600 as int)), 
				' ')
			) tmp as pos,val
		)
select user_id,start_time,end_time,
from_unixtime(unix_timestamp(start_time,'yyyy-MM-dd HH:mm:ss')+3600*pos,
	'yyyy-MM-dd HH') as start_time_every_hh
from b 
order by user_id,start_time_every_hh
  • 结果数据: 结果数据

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

现阶段各个公司的数据慢慢的增多,很多数据都是存放在基于Hadoop的集群上,数据的查询一般使用的是hive,很多公司的数据中台也是使用hive来进行数据处理,本篇文章就来分享下在hive中常用的函数

常用函数

set类设置

  • 查询结果显示表头

set hive.cli.print.header=true;

  • 设置Fetch抓取,不走job

set hive.fetch.task.conversion=more;

  • 展示数据库

set hive.cli.print.current.db=true;

  • 修改是否使用静默

set hive.compute.query.using.stats=false;

日期类函数

  • 当天
select current_date()
运行结果:'2022-08-11'
  • 当月第一天
select trunc(current_date(),'MM')      
运行结果:'2022-08-01'
select date_format(to_date(trunc(current_date(),'MM')),"yyyyMMdd") 
运行结果:'20220801'
  • 当月最后一天
select last_day(current_date)  
运行结果:'2022-08-31'
  • 上个月
select date_format(add_months(CURRENT_DATE,-1),'yyyyMM')
运行结果:'202207'
  • 周几
select pmod(datediff(current_date(),'1900-01-08'),7)+1
运行结果:'4'
  • 获取当前时间戳
select unix_timestamp()
运行结果:'1660212154'

字符串类函数

  • 字符拼接
--concat(参数1,参数2,...参数n)
select concat('a','b','c')
运行结果:'abc'

select concat('a','b',null,'c')   --包含一个null的话,结果为null
运行结果:NULL
  • 字符以分割符进行拼接
--concat_ws(分隔符,参数1,参数2,...参数n)
select concat_ws(',','a','b','c')
运行结果:'a,b,c'

select concat_ws(',','a',null,'c')   --会忽略null
运行结果:'a,c'

select concat_ws(',',null,null,null)  --返回空字符,而不是null
运行结果:''

窗口类函数

  • ROW_NUMBER() –从1开始,按照顺序,生成分组内记录的序列

  • RANK() 生成数据项在分组中的排名,排名相等会在名次中留下空位

  • DENSE_RANK() 生成数据项在分组中的排名,排名相等会在名次中不会留下空位

  • LAG(col,n,DEFAULT) 用于统计窗口内往上第n行值 第一个参数为列名,第二个参数为往上第n行(可选,默认为1),第三个参数为默认值(当往上第n行为NULL时候,取默认值,如不指定,则为NULL)

  • LEAD(col,n,DEFAULT) 用于统计窗口内往下第n行值 第一个参数为列名,第二个参数为往下第n行(可选,默认为1),第三个参数为默认值(当往下第n行为NULL时候,取默认值,如不指定,则为NULL)

更多窗口函数可参考

《Hive分析函数系列文章》:
http://lxw1234.com/archives/2015/07/367.htm

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

平时在跑数据时,需要在查询语句前设置一些set语句,这些set语句中其中有一些是配置hive的各功能,另一些是可以达到优化的目的,本篇文章对一些常用的set语句进行总结

常用set设置

  • 查询结果显示表头

执行完查询语句,输出结果时,会一起把字段的名字也打印出来

set hive.cli.print.header=true;  --默认为false,不打印表头
  • 展示当前使用的数据库

主要是在命令行模式中使用,方便核查是否切换到相应的数据库下

set hive.cli.print.current.db=true;  --默认为false,不显示当前数据库名字
  • 设置是否使用元数据中的统计信息

比如想要看数据一共有多少行的话,一般是从元数据中的统计信息直接获取,但有时这个统计信息没有更新,得到的是历史的统计信息,则需要修改为 false,然后再进行查询,才能统计出准确的数据

set hive.compute.query.using.stats=false;   --默认为true,使用元数据中的统计信息,提升查询效率
  • 设置Fetch抓取,不走job,不用执行MapReduce

一般用于快速获取样例数据,select * from talbe_xxx limit 100

set hive.fetch.task.conversion=more;  
  • 设置查询任取走哪个队列

一般公司的服务器集群中会配置好几个队列,不同的队列优先级不一样,并且资源配置有可能不一样,生产环境的任务肯定优先级高、计算资源多,数据分析的任务一般是单独的队列,计算资源少

set mapreduce.job.queuename=root.db;   --运维人员设置的队列名字
  • 是否开启严格模式

一般运维人员会设置为严格模式 strict,防止大量数据计算占用资源,多出现在笛卡尔积join时;或者查询的是分区表,但没有指定分区,明明sql语句没有逻辑错误,但是一直报错无法运行,可以尝试修改为非严格模式,看是否能运行

set hive.mapred.mode=nonstrict;    --nonstrict,strict   
  • with as 语句存储在本地,从而做到with…as语句只执行一次,来提高效率

对应喜欢用 with as 形式查询的话,可以设置一下这个,来提升效率

set hive.optimize.cte.materialize.threshold=1;
  • 配置计算引擎

Hive底层的计算由分布式计算框架实现,目前支持三种计算引擎,分别是MapReduce、Tez、 Spark,默认为MapReduce

MapReduce引擎:多job串联,基于磁盘,落盘的地方比较多。虽然慢,但一定能跑出结果。一般处理,周、月、年指标。

Spark引擎:虽然在Shuffle过程中也落盘,但是并不是所有算子都需要Shuffle,尤其是多算子过程,中间过程不落盘 DAG有向无环图。 兼顾了可靠性和效率。一般处理天指标。

Tez引擎:完全基于内存。 注意:如果数据量特别大,慎重使用。容易OOM。一般用于快速出结果,数据量比较小的场景。

set hive.execution.engine=mr;    --mr、tez、spark

其他set设置

set hive.exec.parallel=true;    --开启任务并行执行 
set hive.exec.parallel.thread.number=8;   -- 同一个sql允许并行任务的最大线程数
set hive.exec.max.dynamic.partitions=1000           -- 在所有执行MR的节点上,最大一共可以创建多少个动态分区。
set hive.exec.max.dynamic.partitions.pernode=100   -- 在每个执行MR的节点上,最大可以创建多少个动态分区
set hive.auto.convert.join = false;    --取消小表加载至内存中
set hive.mapjoin.smalltable.filesize=25000000;   --设置小表大小

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

对用户每天的访问次数进行统计时,需要对用户访问页面相邻的时间间隔小于30分钟归并为一组(算是一次),这样可以统计出用户每天的访问次数(忽略隔天问题)。这个问题如果用python来处理可能比较方便,可以循环遍历每行,进行两两之间的比较。利用Hive来处理数据,劣势就是不能循环遍历不够灵活,但是也能处理,只是过程相对比较复杂

模拟数据与预想的效果

归并分组

创建测试数据

--创建临时表
create table test.tmp_datashare
(user_id string comment '用户id',
url string comment '网页',
create_time string comment '访问时间')
comment '用户访问日志'
row format delimited fields terminated by '\t' lines terminated by '\n';

--加载数据
load data local inpath '/tmp/datashare.txt' overwrite into table test.tmp_datashare;

测试数据: 测试数据

数据处理过程

  • 数据处理的难点:

1、时间处理需要用到 UNIX_TIMESTAMP 转换为时间戳

2、运用窗口函数 LAG 提取前一行的访问时间

3、再次运用窗口函数 SUM 进行归并分组

  • 具体代码如下:
with a as (select user_id,url,create_time,
			lag(create_time,1) over(partition by user_id order by create_time) as last_1_time
		from clwtest.tmp_datashare
	),
	b as (select user_id,url,create_time,
		case 
			when last_1_time is null then 1
			when (unix_timestamp(create_time,'yyyy-MM-dd HH:mm:ss')-
				unix_timestamp(last_1_time,'yyyy-MM-dd HH:mm:ss'))/60<30 then 0
			else 1
		end as group_tmp
	from a
	),
	c as (select user_id,url,create_time,
		sum(group_tmp) over(partition by user_id order by create_time) as group_id
	from b
	)
select user_id,url,create_time,group_id
from c
order by user_id,create_time

  • 结果数据: 结果数据

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

日期计算平时在业务取数时经常涉及到,但是数据库中经常存放着不同的日期格式,有的存放是时间戳、有的是字符串等,这时需要对其进行转换才能提取到准确的数据,这里介绍的均是hive里面的函数功能,以下内容均是业务的数据需求经常使用的部分

时间戳

unix时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒,一般为10位的整数

一个在线工具:https://tool.lu/timestamp/

时间戳

字符串日期

如:'2021-10-21 19:25:50','2021-10-21 20:25:50.0','2021-10-21 20:25'

日期格式转换

时间戳--->正常的日期格式

  • 获取当前时间戳
select unix_timestamp()
  • 把时间戳转为正常的日期
select from_unixtime(unix_timestamp(),'yyyy-MM-dd hh:mm:ss') as dt
  • 业务中有时存放的是包含毫秒的整数,需要先转换为秒
select from_unixtime(cast(create_time/1000 as bigint),'yyyyMMdd') as dt

字符串日期

假如数据库存放的是格式为:"yyyy-MM-dd hh:mm:ss"

  • 截取日期部分
select substr('2021-10-22 17:34:56',1,10)
2021-10-22
  • 字符串强制转换,获取日期
select to_date('2021-10-22 17:34:56')
2021-10-22
  • 也可以通过date_format实现
select date_format('2021-10-22 17:34:56','yyyy-MM-dd')
2021-10-22

系统当前日期

  • 当前日期
select current_date();
2021-10-22
  • 字符串日期与系统当前日期比较,这个在业务中经常有用到
select substr('2021-10-22 17:34:56',1,10)>current_date()
false

前一日/昨日

select date_sub(current_date(),1);
2021-10-21

前一日12点/昨日12点

在业务中与截取的字符串日期进行比较时用

select concat(date_format(date_sub(current_date(),1),'yyyy-MM-dd'),' ','12');
2021-10-21 12

最近一个月/30天

select date_sub(current_date(),30);
2021-09-22

当月第一天

业务中经常用在滚动计算当月每日的业绩数据

select date_format(to_date(trunc(current_date(),'MM')),"yyyy-MM-dd");
2021-10-01

日期格式转换 yyyyMMdd--->yyyy-MM-dd

select from_unixtime(unix_timestamp('20211022','yyyyMMdd'),"yyyy-MM-dd");
2021-10-22

两个日期相隔天数

select datediff('2021-10-22', '2021-10-01');
21

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

硬件信息及操作系统信息

服务器:戴尔R740

显卡:英伟达 Tesla V100

操作系统:CentOS Linux release 7.8.2003 (Core)

Anaconda安装

从官网下载 Anaconda3-2020.02-Linux-x86_64.sh,运行后一步一步安装

安装成功界面

参考:
https://blog.csdn.net/Gary1_Liu/article/details/81297927 https://www.jianshu.com/p/1888984cad82

Anaconda环境管理

  • 克隆环境

conda create --name newname --clone oldname

例如:conda create --name pytorch1.5 --clone base

  • 创建环境

conda create -n 环境名 python=3.7

例如:conda create -n pytorch1.5 python=3.7

  • 激活环境

conda activate 环境名

例如:conda activate pytorch1.5

激活环境

  • 退出环境

conda deactivate

  • 对虚拟环境中安装额外的包

conda install -n your_env_name [package]

例如:conda install -n pytorch1.5 pytorch==1.5.0 torchvision==0.6.0 cudatoolkit=10.2 -c pytorch

经测试后发现,只要激活了虚拟环境后,用pip直接安装也可以,安装的包是直接在虚拟环境里面

例如:pip install torch==1.5.0 torchvision==0.6.0

  • 删除环境内某个包

conda remove --name 环境名 包名

例如:conda remove --name pytorch1.5 pytorch

  • 查看当前存在哪些虚拟环境

conda env list

  • 查看安装了哪些包

conda list

  • 检查更新当前conda

conda update conda

  • 删除虚拟环境

conda remove --name oldname --all

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号,不定期分享干货

背景

由于业务需要频繁处理大量视频(几十GB),通过公司内网传输太慢,于是就每次处理视频时需要在服务器挂载硬盘或U盘。业务人员给的硬盘或U盘格式有时不一样,目前遇到的格式:NTFS、FAT32、exFAT,这几种格式大家在Windows上基本很常见,于是总结了这些格式的硬盘如何有效挂载到Linux服务器,分享出来供大家参考

  • NTFS挂载
  • FAT32挂载
  • exFAT挂载

NTFS挂载

第一步:安装驱动ntfs-3g

yum install ntfs-3g

第二步:查看硬盘信息(硬盘已通过USB插入服务器)

fdisk -l 

会在最后列出该硬盘的信息,一般是sdb,默认只有1个分区,下面挂载时用的是sdb1

但有的硬盘里面也有2个分区的,如下所示:

Disk identifier: 9B602E4F-E563-4A27-9510-46DEBC0BAA20
#         Start          End    Size  Type            Name
 1           40       409639    200M  EFI System      EFI System Partition
 2       409640   3906961407    1.8T  Microsoft basic My Passport

如果是这种情况,下面挂载时就需要用到sdb2

第三步:挂载硬盘

cd /mnt
mkdir Windows   #挂载时一定要提前创建好该文件夹
mount -t ntfs-3g /dev/sdb1  /mnt/Windows

第四步:解除挂载

umount /dev/sdb1

硬盘挂载基本就以上这四步,下面主要列出其他格式硬盘挂载的重点步骤

FAT32挂载

不需要驱动,可以直接挂载

下面的挂载命令 支持 中文、挂载后不同用户可读写权限,具体参数含义可自行百度查询

第三步:挂载硬盘

mount -t vfat -o iocharset=utf8,umask=000,rw,exec /dev/sdb1 /mnt/Windows

exFAT挂载

第一步:安装驱动fuse-exfatexfat-utils

yum install fuse-exfat
yum install exfat-utils

第三步:挂载硬盘

mount /dev/sdb2  /mnt/Windows

总结

  • 有的格式需要安装驱动,有的不需要
  • 硬盘里面具体要看有几个分区,挂载时指定分区号 sdb1 or sdb2

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注DataShare,不定期分享干货

背景

本地连接远端的服务器,SecureCRT可以说是一大利器,可以保存密码、设置自动登陆等,每次都可以一键直连服务器

最近因公司加强了服务器登陆验证,增加了二次认证,必须用Google Authenticator输入6位动态验证码,才能成功登陆,这样的话每次都得打开手机,手动输入验证码比较麻烦

Python 中有这样的库 pyotp 可以直接生成Google Authenticator输入6位动态验证码,前提是你知道谷歌验证码对应的密钥,一般是在最开始让扫描二维的下方会提示出来 密钥

SecureCRT支持利用一些语言脚本来实现自动登陆,比如:python、vbs,本篇文章来介绍如何利用 python 脚本自动登陆 登陆动作

SecureCRT版本

电脑为Win10操作系统

SecureCRT版本:Version 6.7.0 (build 153)

SecureCRT中的python版本:python2.6 (可以在安装文件里面查看到)

SecureCRT版本 支持python版本

由于没有更新SecureCRT版本,一直用的老版本,支持的python也算是比较老了!!!

最新的SecureCRT版本是支持python3,但是需要进行一些设置,相对比较麻烦,感兴趣的话可以看看这篇文章:

《How-To: Use Python 3.8 with SecureCRT v9.0 for Windows》

https://forums.vandyke.com/showthread.php?t=14295

遇到问题

参考网上分享的一些例子,在 import pyotp 时总是会报错

《python 实现 jumpserver 自动登录》
https://mp.weixin.qq.com/s/aLazW8WUVfvsICnHXes3CA

报错

即使添加了python包路径也不行,一直报错,pyotp 是兼容python2、python3所有版本

添加路径

解决方法

通过提示可以看出,import sys 时并没有报错,说明python内置的包,是可以直接导入使用的,经过测试把 pyotp 源码中涉及到生成动态码的库import时,没有报错,说明已经走通了

这时就需要剖析 pyotp 源码,有哪些是生成生成动态码必须的,把冗余的代码全部剔除即可,经过分析也就是两个类有用,如下所示:

class OTP(object):
    def __init__(self, s, digits=6, digest= hashlib.sha1, name= None,issuer= None):
        self.digits = digits
        self.digest = digest
        self.secret = s
        self.name = name or 'Secret'
        self.issuer = issuer
        
    def generate_otp(self, input):
        if input < 0:
            raise ValueError('input must be positive integer')
        hasher = hmac.new(self.byte_secret(), self.int_to_bytestring(input), self.digest)
        hmac_hash = bytearray(hasher.digest())
        offset = hmac_hash[-1] & 0xf
        code = ((hmac_hash[offset] & 0x7f) << 24 |
                (hmac_hash[offset + 1] & 0xff) << 16 |
                (hmac_hash[offset + 2] & 0xff) << 8 |
                (hmac_hash[offset + 3] & 0xff))
        str_code = str(code % 10 ** self.digits)
        while len(str_code) < self.digits:
            str_code = '0' + str_code

        return str_code

    def byte_secret(self):
        secret = self.secret
        missing_padding = len(secret) % 8
        if missing_padding != 0:
            secret += '=' * (8 - missing_padding)
        return base64.b32decode(secret, casefold=True)

    @staticmethod
    def int_to_bytestring(i, padding= 8):
        result = bytearray()
        while i != 0:
            result.append(i & 0xFF)
            i >>= 8
        # It's necessary to convert the final result from bytearray to bytes
        # because the hmac functions in python 2.6 and 3.3 don't work with
        # bytearray
        return bytes(bytearray(reversed(result)).rjust(padding, b'\0'))
        
        
class TOTP(OTP):
    def __init__(self, s, digits= 6, digest=hashlib.sha1, name=None,issuer=None, interval= 30):
        self.interval = interval
        super(TOTP,self).__init__(s=s, digits=digits, digest=digest, name=name, issuer=issuer)

    def now(self):
        return self.generate_otp(self.timecode(datetime.datetime.now()))

    def timecode(self, for_time):
        if for_time.tzinfo:
            return int(calendar.timegm(for_time.utctimetuple()) / self.interval)
        else:
            return int(time.mktime(for_time.timetuple()) / self.interval)

完整代码

以下为SecureCRT利用Python脚本自动登陆服务器的完整代码:

# $language = "Python"
# $interface = "1.0"

import calendar
import datetime
import hashlib
import time
import base64
import hmac

class OTP(object):
    def __init__(self, s, digits=6, digest= hashlib.sha1, name= None,issuer= None):
        self.digits = digits
        self.digest = digest
        self.secret = s
        self.name = name or 'Secret'
        self.issuer = issuer
        
    def generate_otp(self, input):
        if input < 0:
            raise ValueError('input must be positive integer')
        hasher = hmac.new(self.byte_secret(), self.int_to_bytestring(input), self.digest)
        hmac_hash = bytearray(hasher.digest())
        offset = hmac_hash[-1] & 0xf
        code = ((hmac_hash[offset] & 0x7f) << 24 |
                (hmac_hash[offset + 1] & 0xff) << 16 |
                (hmac_hash[offset + 2] & 0xff) << 8 |
                (hmac_hash[offset + 3] & 0xff))
        str_code = str(code % 10 ** self.digits)
        while len(str_code) < self.digits:
            str_code = '0' + str_code

        return str_code

    def byte_secret(self):
        secret = self.secret
        missing_padding = len(secret) % 8
        if missing_padding != 0:
            secret += '=' * (8 - missing_padding)
        return base64.b32decode(secret, casefold=True)

    @staticmethod
    def int_to_bytestring(i, padding= 8):
        result = bytearray()
        while i != 0:
            result.append(i & 0xFF)
            i >>= 8
        # It's necessary to convert the final result from bytearray to bytes
        # because the hmac functions in python 2.6 and 3.3 don't work with
        # bytearray
        return bytes(bytearray(reversed(result)).rjust(padding, b'\0'))
        
        
class TOTP(OTP):
    def __init__(self, s, digits= 6, digest=hashlib.sha1, name=None,issuer=None, interval= 30):
        self.interval = interval
        super(TOTP,self).__init__(s=s, digits=digits, digest=digest, name=name, issuer=issuer)

    def now(self):
        return self.generate_otp(self.timecode(datetime.datetime.now()))

    def timecode(self, for_time):
        if for_time.tzinfo:
            return int(calendar.timegm(for_time.utctimetuple()) / self.interval)
        else:
            return int(time.mktime(for_time.timetuple()) / self.interval)

username='aaa'
password='aaa'
google_author_secret_key='自己的密钥'

def Main():
    tab = crt.GetScriptTab()
    if tab.Session.Connected != True:
        crt.Dialog.MessageBox("Session Not Connected")
        return
    tab.Screen.Synchronous = True
    
   
    tab.Screen.WaitForStrings(['Password: '])
    tab.Screen.Send(password+'\r\n')
    tab.Screen.WaitForStrings(['Please enter 6 digits.[MFA auth]: '])
    vc = TOTP(google_author_secret_key).now()
    tab.Screen.Send("{vc}\r\n".format(vc=vc))
    
    return


Main()

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

也许你有这样的疑问,数据分析师为什么要了解Linux?这不是开发人员应该了解的吗?把Windows+SQL+Excel+Python玩的精通,不香吗?

以上的疑问也许处有人会提出,但随着个人的职业成长,企业数字化的发展,终究会与Linux系统打交道,比如:在数据挖掘时,大量的数据需要做分析、特征提取,然后跑模型,这些任务在个人的Windows系统基本完全做不了,只能在Linux服务器上来完成

备注:在大厂就职的同学 and 公司基建做的非常好的,可以忽略本文,说明你们公司比本小编曾经就职的某手好点,当然多学习一些知识,对自己百利而无一害,活到老学到老

以下介绍的命令是基于: Centos系统,Linux的一个发行版

常用命令

  • pwd

当前所处的文件夹位置

pwd

  • ls

列出当前文件夹里面的文件及子文件夹

ls

  • ll ll

该命令相当于 ls -l

展示文件夹里面的文件及子文件夹详细信息,有点类似在Windows里面查看文件夹时以详细信息方式展示一样

ll

  • cd

切换目录,换到别的文件夹

cd

  • mkdir

创建一个新建文件夹

mkdir

  • cp

cp 文件 新文件夹

复制文件到新的文件夹里面,copy的简写

cp

cp -r 文件夹 新文件夹

复制整个文件夹到新的文件夹里面,需要添加 -r 参数,进行递归式复制

cp

  • mv

mv 文件 新文件夹

移动文件到新文件夹,move 的简写

mv

mv 文件夹 新文件夹

移动文件夹到新文件夹

mv

  • cat

cat 文件名

展示文件里面的内容

cat

  • sort

sort 文件名

对文件里面的行进行排序

sort

sort -u 文件名

对文件里面的行进行去重并且排序,-u 是 unique 的简写

sort

  • head

显示文件的前几行内容,在默认情况下,head命令显示文件的头10行内容,-n 参数可以指定要显示的行数

head

  • tail

显示文件的后几行内容,在默认情况下,tail显示最后 10 行,-n 参数可以指定要显示的行数

tail

  • top

实时动态显示各进程的情况,可以按 M与T 进行可视化变化,可以显示为进度条样式

top

  • df -h

显示目前在 Linux 系统上的文件系统使用情况统计磁盘,-h 参数代表使用人类可读的格式 human-readable,是human的简写

df

  • ps -ef

查看服务器上所有运行的进程,类似Windows的查看任务管理器,PID 代表进程号

ps

  • kill -9 进程号

强制杀死进程,类似Windows的在任务管理器中结束某个进程任务,用上面的 ps 命令查出进程号后,可以直接强制退出该进程

  • rm

删除文件或者文件夹,谨慎使用,删除掉就不容易恢复,不像Windows在回收站可以找回

rm 文件名

删除文件

rm

rm -r 文件夹 删除文夹,-r 参数为递归删除文件夹及子文件夹里面的文件

rm

好用的学习网站

以上介绍的只是几个常用的命令,下面列出几个网站,个人感觉这几个比较好用,供大家可参考学习

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

小编在刚开始学习Python时,是在Python官网下载的原生版本,用的是自带的编码环境,后来了解到在数据分析、数据科学领域用Jupyter notebook比较好,于是直到现在也是一直在用Jupyter notebook,也偶尔用PyCharm做开发。在数据分析与处理中Jupyter notebook还是很方便,可以直接查看数据,可以写文档,可以画图 等很多优点,感觉Jupyter notebook 就是是为了数据分析、数据挖掘、机器学习而生的

如果用过Jupyter notebook,大家都知道,它是一个网页界面,服务端和客户端分离,每次启动都会在后台运行一个cmd/terminal窗口,可以理解为服务端,而使用的浏览器界面,可以理解为客户端。既然是服务端与客户端分开的,那么服务端就可以部署到服务器上,可以充分利用服务器的计算资源、存储资源,更甚者可以用GPU资源。其实在服务器部署Jupyter notebook,做机器学习的同学,应该对这个很熟悉,但是不一定亲手部署过 Jupyter notebook

浏览器-客户端

客户端

cmd-服务端

服务端

必要前提!!!

需要对Linux服务器要有所了解,如果在工作中压根就没使用过服务器,且不知道ssh,那小编劝你暂时先别看这篇文章,可以先去多学一些Python知识,后面随着知识的积累,慢慢的你就会接触到服务器

小编服务器环境

硬件:谷歌云虚拟机实例,1G内存,100G硬盘

系统:64位 Debian,Debian GNU/Linux 11

服务器部署流程

在服务器上安装Jupyter notebook,一共分为4步,最终实现在本地电脑来访问

因为Anaconda自带Jupyter notebook,并且可以创建虚拟环境,使用起来非常方便,所以强烈推荐使用Anaconda,可以让你少采坑

第1步:下载Anaconda

Anaconda 下载地址:https://www.anaconda.com/download#downloads 下载Anaconda

在服务器上直接下载(谷歌服务器下载很快)

wget https://repo.anaconda.com/archive/Anaconda3-2023.09-0-Linux-x86_64.sh

服务器下载

第2步:安装Anaconda

在服务器上直接安装Anaconda,在安装中一直点击回车或空格键,在许可条款、最后配置的地方输入 yes 即可

在安装时确保硬盘存储空间要足够,小编在安装是最开始只有10G硬盘资源,一直报错,后来扩大硬盘后解决了

安装完成后,命令行提示符前会有多了个 (base),这个是Anaconda默认的基础虚假环境,小编下载的是截止发文章时最新版Anaconda,里面集成的是 Python 3.11.5

bash Anaconda3-2023.09-0-Linux-x86_64.sh

安装完成

第3步:对Jupyter notebook进行配置

  • 生成配置文件,运行后会显示配置文件的存放位置
jupyter notebook --generate-config

生成配置文件

  • 对配置文件进行修改
## The IP address the notebook server will listen on.
#  Default: 'localhost'
# 设置可以访问的ip, 默认是localhost, 将其改为 '*'
c.NotebookApp.ip = '*'


## The directory to use for notebooks and kernels.
#  Default: ''
# 默认打开目录
c.NotebookApp.notebook_dir = '/home/data123share66/python'


## Whether to open in a browser after starting.
#                          The specific browser used is platform dependent and
#                          determined by the python standard library `webbrowser`
#                          module, unless it is overridden using the --browser
#                          (NotebookApp.browser) configuration option.
#  Default: True
# Jupyter notebook启动后是否打开浏览器, 设为 False 即可
c.NotebookApp.open_browser = False


## Hashed password to use for web authentication.
#  
#                        To generate, type in a python/IPython shell:
#  
#                          from notebook.auth import passwd; passwd()
#  
#                        The string should be of the form type:salt:hashed-
#  password.
#  Default: ''
c.NotebookApp.password = 'argon2:$argon2id$v=19$m=10240,t=10,p=8$Ny6WDdoLBm88cUMyOqgNqg$s3WObP81eU51RT2j8D8DULPM1OAPOnzYfODW8olB0xw'

小编这里在生成密码是用的 datashare ,在输入时屏幕不会显示

生成密码

第4步:启动Jupyter notebook,并在本地电脑远程访问

  • 1、服务器启动Jupyter notebook
jupyter notebook --ip=0.0.0.0 --port=9999
  • 2、在本地电脑浏览器远程访问 在浏览器输入服务器的IP地址:9999,小编这里是34.81.173.39:9999,访问后,会出现如下页面

远程访问

然后输入密码 datashare 进行登录,成功登录后界面如下所示:

登录后界面

  • 3、创建一个notebook,查看python版本 查看python版本

Jupyter 好用的扩展插件

  • 1、安装jupyter_contrib_nbextensions 该插件会扩展jupyter的很多功能,如目录,自动补全等,在服务器终端依次运行如下命令
pip install jupyter_contrib_nbextensions

jupyter-contrib-nbextension install --user

打开jupyter会发现多了一个菜单栏 Nbextension Nbextension 对Nbextension进行配置,勾选需要的功能 Nbextension配置

  • 2、安装nb_conda
conda install nb_conda

安装完成后,需要在服务器重新启动一下Jupyter notebook,会发现多了一个菜单栏 Conda nb_conda 在服务器创建一个虚拟环境 python312,然后刷新一下页面,就可以看到虚拟环境 python312

conda create -n python312 python=3.12

虚拟环境

多个Python版本kernel配置

虽然上面已经创建了虚拟环境,并显示出来了,但是在创建新的notebook时并显示python312,因为python312存在不同的虚拟环境里面,这个需要我们再进行配置 单个kernel

Jupyter Notebook允许用户在同一个notebook中使用多个不同的IPython内核

  • 1、安装Jupyter Notebook和IPython内核
conda create -n python312 python=3.12   #上面安装过的可以忽略
conda activate python312
pip install jupyter
pip install ipykernel
pip install ipywidgets
  • 2、安装新的kernel内核
conda activate python312   #切换虚拟环境
ipython kernel install --name "python312" --user
  • 3、服务器端重新启动Jupyter notebook 建一个python312内核的notebook,查看当前内核的python解释器版本 切换为新内核

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

背景

标准差这个指标在平时使用比较多,主要是用来计算数据的离散程度,在Excel中有相关的函数,可以直接来计算,其他的编程语言里面也有相关的函数。

Excel中提供了2个函数 stdev.S 和 stdev.P ,都可以用来计算标准差,但这两者应该如何合理使用呢?又有什么区别呢?本篇文章将对这两个函数进行详细的讲解

stdev.S和stdev.P区别

1、先看微软文档给出的解释 stdev.S

stdev.P

2、源自网络文章的理解 当你只知道一小部分样本,想要通过其 【估算】 这部分 【样本代表的总体】【标准差】 ——选择stdev.S(2010版之后叫stdev.S,老版叫stdev。这个S就是sample,样本的意思)

当你拿到的数据已经是 整体数据 了,想要计算这部分数据精确的标准差——选择stdev.P(2010版之后叫stdev.P,老版叫stdevP。这个P我猜是population,在统计学上有“总体”之意)

3、公式对比 stdev.S 计算公式: $$\sqrt{\frac{\sum(x-\overline{x})^2}{n-1}}$$

stdev.P 计算公式: $$\sqrt{\frac{\sum(x-\overline{x})^2}{n}}$$

从计算公式可以看出,唯一的区别就是根号中的分母不一样,这个涉及到自由度的概念(理解起来比较复杂),我们可以直接硬记住这个公式即可

4、总结

  • 数据是抽取的样本时用stdev.S 【其他编程语言中用的是这个】
  • 数据是全量时用stdev.P

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

在Excel里面数据去重方法比较多,目前用的比较多的有:

1. 数据--筛选---高级筛选

2. 数据透视表

但以上这两种方法都有局限性,比如需要去重的区域是多列数据


下面介绍一种通过VBA编写宏程序来进行数据去重,可以去重多列数据

先把代码呈现出来,再进行解释:


Sub 字典方法()

    Dim rng As Range       '定义一个区域,用于接收需要去重的单元格区域

    Set rng = Application.InputBox("请指定去重区域", "数据源区域", , , , , , 8)     '可以是一列,也可以是多列

    If rng Is Nothing Then End         '如果没有选择区域,则退出,注:是End,不是End IF



    Dim arr    '定义一个数组,用于存储去重区域的值

    arr = rng.value



    Dim a

    On Error Resume Next         '这里需要忽略错误,后面添加重复键值会引发错误



    Dim dic As Object    

    Set dic = CreateObject("scripting.dictionary")            '引用字典对象



    For Each a In arr

        If Len(a) > 0 Then     

            dic.Add CStr(a), ""     '往字典里面添加键值

        End If

    Next a

    Err.Clear



    Set rng = Application.InputBox("请指定结果存放区域,单个单元格即可", "存放区域", , , , , , 8)



    If Err <> 0 Then End



    rng(1).Resize(dic.Count, 1) = WorksheetFunction.Transpose(dic.Keys)         '往单元格里面写入去重后的数据



End Sub


这里主要运用的字典的键值唯一性,如果向字典里面添加已经存在的键,则会引发错误,所以中间部分需要忽略错误

数组+字典在VBA里面可以提升程序运行效率,如想提升VBA程序运行效率,可多运用数组、字典

俗话说的好,磨刀不误砍柴工,对于以技术来吃饭的人来说,好用的工具、好用的网站、好用的代码、好用的方法(函数),都是技术人所必须的。

使用过Chatgpt后,大家是不是都坐不住了,甚至会赞叹到这么牛逼,但目前国内还不能用,经过各大厂商在昼夜加班制作模型后,截止当前小编就成功使用上了阿里的通义千问,百度的申请已不知道提了有多久,目前还没通过

撇开大模型这个工具之外,平时使用最多的也就是搜索引擎工具,但国内目前只能使用必应,(如果你平时使用的是百度,请忽略,小编感觉它是个广告器),如果有条件的话,大家也可以用谷歌,(别对小编说信息安全,看看百度的广告就知道了)

搜索引擎相对更准确是谷歌,举个不恰当的例子:从搜一个问题到找到满意的答案时间,谷歌大概需要1分钟,必应需要3分钟,百度需要1个小时,足见效率的高低了

工欲善其事必先利其器,如果你当前浏览器的默认搜索引擎还是百度,赶紧切换为必应吧

递推 vs 递归

递推

我们都熟知的正向思维,从前往后、从小到大、由易到难、由局部到整体 比如5的阶乘,要从小到大一个个乘起来,即 5!= 1 × 2 × 3 × 4 × 5

这种计算思维是我们从小学到初中,到高中一直培养的习惯,甚至到现在的编程算法,我们大部分人也是运用这种思维来解决问题,比如大家面试经常遇到的问题,要求编写一个斐波那契函数f(n),计算给定 n 的函数值

递归

递归属于逆向思维,需要倒过来思考问题,从大到小、由整体到局部的思维,例如上面求 5 的阶乘,先假定4!是已知的,然后再乘以 5 即可。当然,大家会问,那 4! 怎么计算呢? 很简单,采用同样的方法,3!× 4,只要计算出 3!乘以 4 即可。同样的方法,来计算 3!,直到最后的 1!,我们知道它就等于 1 。接下来,就是倒推回所有的结果,从 1!、2!一直倒推回 5!

递归的思想有两个明显的妙处:
1、只要解决当前一步的问题,就能解决全部的问题
2、复制同一个过程,就能得到结果

要把问题通过递归的细想来解决,必须满足两个前提条件:
1、每一个问题在形式上都是相同的,否则无法通过同一个过程完成不同阶段的计算(也就是必须要满足过程可复制)
2、必须确定好结束条件,否则就像 “从前有座山” 那个故事里的情节,永远无法结束

吴军老师的原话:很多人在学计算课程时非常不喜欢递归这种不直观的逆向思维,觉得像阶乘运算这种从小到大一个个相乘就可以了,何必那么复杂地倒着计算呢?原因很简单,很多问题只有倒着才能想清楚。这一关如果过不了,在计算机领域做一辈子技术也出不了师

小编见解:递归的思维,用到的地方其实很多,比如,数据分析里面,我们正着去分析可能就不通,但我们反向去思考可能就解决了

信息编码的重要性

计算机中的所有内容都是以二进制进行编码的,比如看到的文字,在硬盘、内存、网络传输时中都是以编码形式存储的,如我们熟悉的UTF-8编码,还有最开始学计算机编程时了解的ASCII (American Standard Code for Information Interchange) 等,都是编码在计算机中的应用。信息的编码和有效表示是计算机科学和工程的基础,所以我们要理解这些基础的原理,这样有助于我们深入了解计算机。

当然,二进制编码对于人类来讲很不直观,便产生了很多便于人类辨识的等价代码,比如在MIPS处理器中,用 001000 代表加法运算,如果这样写程序几乎没有人能记住,出错也很难以检查,于是人们就将 ADD 这三个字母和 001000 这条代码对应起来。

下面以书中的案例,来更深入的向大家介绍编码的重要性,这道题 吴军老师说也是面试题,大家不妨把自己当作面试者,也一起思考下怎么来解答

案例---分割黄金问题:

泰勒是一位雇主,雇用鲍尼为自己新建的房子铺设院子里的地砖,这是一个七天工作量的活。泰勒答应一共支付一根金条作为报酬,但是鲍尼每天支付他 1/7 的工资,泰勒答应了。现在,请问你如何在金条上切两刀,保证每天正好能支付鲍尼 1/7 的工资?

(思考2分钟,再往下看)

小编看了这道题也没有想出来怎么切分,但看了答案感觉确实妙不可言,里面巧妙的运用了编码的思想,下面就给出答案

在金条的 1/7 的地方切一刀,在 3/7 的地方再切一刀,这样金条就变成了三个小金块,质量分别是 1/7、2/7、4/7 的金条质量。接下来,我们就要用1、2、4 这三个数字表示出 1 ~ 7 这七个数字,具体的表示方法如下:

1 = 1

2 = 2

3 = 2 + 1

4 = 4

5 = 4 + 1

6 = 4 + 2

7 = 4 + 2 + 1

利用上面的公式,在发工资时,泰勒第一天给鲍尼 1/7 金条质量的那一块黄金;第二天给鲍尼 2/7 的那一块,并要求对方交回先前 1/7 的那一块;第三天,再给鲍尼 1/7 的那一块,这样鲍尼就得到了 3/7 金条质量的黄金;第四天,给鲍尼 4/7 的那一块,但是要求他将之前给的那两小块金条交回;............到了第七天,把所有的黄金都给鲍尼即可。

这道题解法的关键是 1、2、4 这三个数字能够表达 1 ~ 7 的所有数字。那为什么 1、2、4可以呢?因为它们分别是二进制的 “个位数”、“十位数”、“百位数”。因为二进制只有 0、1 两个数字,所以每一个进位是 0 或者是 1 的组合,就能表示各种数字。我们不妨再用二进制把前面七个等式重写一遍就一清二楚了(二进制以 0b 开头)

1 = 0b001

2 = 0b010

3 = 0b011 = 0b010 + 0b001

4 = 0b100

5 = 0b101 = 0b100 + 0b001

6 = 0b110 = 0b100 + 0b010

7 = 0b111 = 0b100 + 0b010 + 0b001

通过这道案例题,大家是不是对编码有了更进一步的了解,原来编码这么重要,其实大家在编程时可能已经运用过,只是没有深入思考而已,以下摘自微软开发者文档,文件的操作: 编码

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货