亚灿网志 - Python
type: channel
link: https://blog.manyacan.com/tag/Python/
items: 「Python程序设计」供水调度项目总结
items: Python数据分析——以我硕士毕业论文为例
items: Mantel Test
items: 「Python」数据分析奇技淫巧
items: 「Python」用户消费行为分析
items: 「Python」矩阵、向量的循环遍历
items: 「Python」Generator生成器
items: 「Python」列表推导式
items: 「Python」装饰器到底是个啥？
items: 数据中心化与标准化

「Python程序设计」供水调度项目总结
type: item
date: 2023-12-16T19:56:00+08:00
description: 🔛 引八月初的时候，博导跟我介绍了这个项目，大概内容就是对某市供水管网中的泵站与清水池进行优化调度。第一次博导跟我探讨这个项目的时候，我还是有点忐忑，觉得万一自己干不好岂不是砸了老师的招牌。但是我导说了句特别有安全感的话：没事，只管干吧，搞不出来有我给你兜着！安全感直接拉满，奥里给，干！还得是我导儿。历时4个多月，十几次的每周PPT工作汇报，终于完成了模型的算法设计、实际运维逻辑匹配、数据库输入输出对接、线上部署等工作。在这个项目过程中，也经历过很多次的绝望、失落、喜悦，以及无数次的DEBUG，感觉自己还是学到了很多东西的。这篇博客就来记录下整个项目下来我自己的一些学习与心得体会。在上次的组会过程中，我其实已经进行了一个大概的总结，所以说接下来就按照组会PPT中的内容进行一个记录吧~组会汇报⏱️ 考虑时间成本Quote / 参考面对未知的事物，试错过程需要花费大量时间成本，凡事一定要提前准备，预备好抵抗未知错误的缓冲区。对于任何一件未知的事（这个未知是指没有别人做过类似的事可以给你参考），那么就一定要提前考虑到遇到未知错误所花费的时间成本。在项目开始前，博导说预备时间是两个半月，然后我们就先按两个月的来，给最后留半个月的时间缓冲。我当时觉得时间还是很充裕的。然而实际情况做下来，我们花费了将近4个多月，几乎是原来时间的两倍了。首先是第一个问题：项目开始前一定要做充分的调研，大方向不能错，或者说尽量少错。在项目开始前，与甲方多次沟通交流，确定了的方案是“基于历史数据驱动的智能调度模型研究”。从八月到国庆节前，我们也是一直按照这个方向来做的。结果到了国庆节前发现，我们所走的路是存在致命问题的，不得已又改变路线，选择了“基于区间流量平衡与遗传算法的智能调度模型研究”，这也就意味着我们前期浪费了巨多时间做了无用功。其实选择的这个新方案，我导在第一次跟我讲这个项目的时候就提到了这种方案。因为对于一个泵站来说，其水泵开关的组合非常适合使用遗传算法（Genetic Algorithm, GA）进行求解。只是我当时根本不知道遗传算法是个什么东西，所以没有GET到我导的意思。现在看来，我导对大方向的把握真是有点东西，如果一开始就按照我导的思路做，也许我们能减少很多无用功了。第二个感受比较深的过程是在做项目前后端数据库对接的时候。简单来说，我负责模型的构建，模型输入输出的数据需要从数据库中读取或写入，另外一个团队负责WEB前端的开发，前端显示的内容就是从数据库中读取的。这里，犯了一个很严重的逻辑问题。直接与数据库对接的是前端和模型，那么前端就应该在与模型商议数据库结构之前，先确定了用户的需求。但是实际上前端团队并不是这样做的，而是边与用户沟通加入新的需求边与模型端沟通确定写入数据格式。这就造成了一个问题，前端一边与模型一起对接数据库，一边与顾客商议需求，这就给我的模型带来了很大困扰，我对我的模型输出进行了多次很大结构上的调整，花费了很多时间。所以说，选择与靠谱的团队进行合作还是很重要的😑。🤌 嘴上说，心里想, 不如手上做Quote / 参考不知道什么行不行，那就先动手试试，不动手永远不知道结果。动手了还真可能一个个慢慢就解决了。上面也提到了，在国庆前后我们准备更换新的工作路线。我导跟我提到了GA，说GA非常适合我们当时所遇到的问题，但是当时的我对于GA一窍不通。对于未知的事物，人们的本能总是恐惧，所以总是想逃避。当时我的一听到“算法”两个字，似乎就感觉一座大山压在我的身上，要让我迅速学习一个算法并应用到实际项目，这对我来说太难了。所以，那天我还是想看看以前的路到底还有没有办法能够继续走得通。一直到最后，发现老路实在是走不下去了，我才“逼上梁山”开始学习GA。但是当我真的开始学习GA，发现它其实并没有想象中的那么难。从初识到应用到自己的项目里，前后也就是三五天的时间。通过这件事就说明两点：要对自己有信心，面对未知事物，肯定会遇到困难。但是要相信自己——只用功夫深，铁棒磨成针！！！凡事要一点点来，不用心急，每天进步一点点——水滴石穿！！！🐛 先谋大略，后顾细微Quote / 参考技术应用于实际工程，需要考虑太多太多太多的未知情况，前期尽量去考虑照顾到，大方向不要出错误，后期预留一定的时间慢慢调试实际运行中的小问题。如果一项技术的科学研究已经发展到了98%，那么能将其30%成功应用到实际就很不错了， 科学与技术之间还是有很多隔阂的。这个隔阂，其实指的就是将科学应用到实际过程中需要协调的未知情况，因为科学实验大部分是在较为理想的环境中进行的。可能并没有什么好的办法能够消除这种隔阂的，只能说是在技术应用的过程中就尽量多的去考虑实际中可能遇到的情况。然后，还要在实际部署后，预留大量的时间去DEBUG。💡 君子生非异也，善假于物也Quote / 参考擅用Chat…

Python数据分析——以我硕士毕业论文为例
type: item
date: 2023-03-10T23:05:00+08:00
description: Introduction / 引言大学期间用来打发无聊时间学的Python没想到竟然在写毕业论文的时候用处这么大，整个硕士论文所做研究，从前期的数据整理、数据分析，到最后的数据可视化我基本上都使用Python来完成，这篇博客就来分享下我毕业论文课题中所做数据分析相关的Python代码。本博文所有相关的代码都上传在GitHub仓库：Data-Analysis-for-Thesis，如果帮到你了，记得给我来个Star☺️，也可以顺便去参观下我的个人博客亚灿网志。Python环境配置环境安装首先是在Python官网下载你计算机对应的Python软件，然后安装。安装过程基本都是傻瓜式，不做过多叙述，一路回车即可。Python官网IDE选择然后就是IDE（Integrated Development Environment）的配置，最常见的肯定是VSCode，特点是足够轻量化且免费。但是我选择的是PyCharm，我选择它的理由是以前学Python的时候就用的它，因此快捷键什么的都比较熟悉。PyCharm正版是收费的，教育版可以申请一年的使用权限，破解版的自行百度即可。VSCodePyCharmWarning / 注意Pycharm也是可以申请教育版嘀，但是教育版并不支持编写Jupyter Notebook的.ipynb文件。Tips / 提示关于IDE的选择大家没必要纠结，新手不太了解想简单上手的话就选择VSCode；用过PyCharm并且熟练怎么破解安装的那就选择PyCharm。包的安装数据矩阵分析及处理：Pandas、Numpy、Math、Scipy；绘图可视化：Matplotlib、Seaborn；其他包：hues可以在控制台打印出彩色的提示信息，用法也比较简单，官方手册。hues的简单用法数据整理及预处理数据介绍论文所做内容简单来说就是对不同样本点的多个指标进行相关性分析，具体的数据格式就是：样本点变量1变量2变量nD-H1122424...L-N2324232...............基本常量的定义IMG_TYPE = '.svg' # 出图格式，我选择矢量图svg DATA_PATH, EXPORT_PATH, DPI = './data/', './export/', 300 # 数据存储路径、出图写入路径、出图DPI ROW_NUM, COL_NUM = 5, 3 # 对于多子图的图片，定义默认布图网格为5×3文件读取读取csv文件需要使用pandas的pd.read_csv()方法，具体的参数有：index_col：设置行索引为哪一列，可以使用序号或者列名称；sep：csv文件中的分隔符，默认常见的用法都可以自动识别，不需要设置；header：设置表头，参数为None就是没有表头，设置为n就是把第n行读取为表头；names：设置列名称，参数为list；usecols：仅读取文件内某几列。Quote / 参考具体用法可以参考李庆辉所著《深入浅出Pandas——利用Python进行数据处理与分析》3.2章 读取CSV（PDF P89）。数据表合并首先遇到的第一个需求就是，所有样本点的列变量存储在不同的数据表中，比如，样本点的指标分为上覆水的指标与沉积物的指标两部分，分别存储在两个或者多个数据表中，那么如何将两个或者多个数据表进行合并呢？all_df_0 = pd.concat([gene_abundance, sediment_env_df, eems_df, overlying_water_env_df, sediment_size_df], axis=1) # 将五个数据表按照行索引合并Quote / 参考具体用法可以参考李庆辉所著《深入浅出Pandas——利用Python进行数据处理与分析》7.2章 数据连接pd.concat（PDF P274）。根据行索引为每个样本点设置分类行索引也就是每个样本点的标记名，分别为“D-H1”、“L-N3”之类的，其中第一个字符的值为“D”、“L”、“W”，分别代表枯水期（Dry Season）、平水期（Level Season）、丰水期（Wet Season）；第二个字符为一个分隔符“-”；第三个字符的值为“N”、“P”、“H”，分别代表三条不同的河流，南淝河、派河、杭埠河；最后的数字代表是第几个样本点。例如：“D-H1”代表枯水期杭埠河第一个样本点、“L-N3”代表平水期南淝河第三个样本点。那么问题来了，我想要为合并后的数据表新增两列“River”、“Period”，分别来反应这个样本点的属性，应该如何实现呢？样本点变量1变量2变量nRiverPeriodD-H1122424...Hangbu RiverDry SeasonL-N2324232...Nanfei RiverL…

Mantel Test
type: item
date: 2023-03-10T22:55:00+08:00
description: Tips / 提示这篇博文将是你可以在所有中文搜索引擎里找得到的最详细的关于Mantel Test的中文分析讲解。起源在一次课题组师兄汇报的时候，我第一听说了Mantel Test，当时第一眼就被这个漂亮的图形所吸引，所以就想着以后也能用到自己的文章里，便自己花时间了解了下。全网能找到这张图的资料，最早的时间是2015年，在Science中发表的一篇文章里第一次出现了这张图。SUNAGAWA S, COELHO L P, CHAFFRON S, et al. Structure and function of the global ocean microbiome[J]. Science, 2015, 348(6237): 1261359.只能说不愧能发Science，文章里面的每一张图都是经过精心处理的。介绍1967年，美国国立卫生研究院（National Institutes of Health）的生物统计学家Nathan Mantel提出了Mantel Test分析方法。在统计学中，传统相关系数只能用于计算分析一个数据矩阵中每两列变量之间的相关性，而在面对两个矩阵之间的相关性时就一筹莫展。Mantel Test的出现克服了传统相关系数的这一缺陷，该方法本质上是分析两个矩阵之间的相关性。自Mantel Test推出以来，其在微生物群落生态学、动植物种群遗传学等领域都得到快速发展与应用，在分析微生物菌群与环境因子之间的相关性方面更是得到了广泛的应用。在使用Mantel Test分析环境因子与微生物群落结构之间的相关性时，通常对微生物群落OTU数据矩阵使用Bray-Curtis相异度（Bray-Curtis dissimilarity）来计算微生物群落结构之间的差异性；而对环境变量数据矩阵采用欧氏距离（Euclidean Distances）进行差异性计算。Mantel Test的分析过程主要包括：分别使用各自的距离公式计算两个数据矩阵的距离矩阵，然后将两个距离矩阵进行压缩得到两个压缩距离列，然后计算这两列的相关性（一般都采用皮尔逊pearson相关性指数）；在完成一次计算后，对原数据矩阵中的一列或者两列进行置换，重新计算距离公式以及压缩距离公式，计算新的相关性系数（r值）；经过成千上万次的置换后，观察实际数据的r值在经过多次置换后所得的r值分布中的位置，如果跟随机置换得到的结果站队较近，则说明相关性不显著，相反则说明两个矩阵具有较强的显著相关性。讲解下面结合我查阅的一些中外文资料，对Mantel Test做出自己的理解，如果有错误的地方，恳请指正交流（文末有微信）。图形讲解右侧上三角首先来看图形右半部分，这部分大家都很常见，是一个相关性热图，它代表了一个数据矩阵中每两列之间的相关性。而计算相关性的算法一般都选择Pearson相关。皮尔逊（Pearson）相关（r），它测量两个变量（x和y）之间的线性相关性。它也称为参数相关性检验，因为它取决于数据的分布。仅当x和y来自正态分布时才可以使用它。y = f（x）的图称为线性回归曲线。举个例子：Oxygen与Temperature的相关性为深蓝色，对比最后边色条可以发现，深蓝色对应的相关性为-0.8~-1.0之间，说明其两者呈现出很强的相关性（相关性强弱看绝对值），且呈现负相关（相关性方向看正负）。左侧下三角再来看图形的左侧，左侧反映出来的也是相关性（r值），只不过除了相关性之外还反映出了一个显著性（p值），在相关性分析中，r值代表相关性，p（sig）值代表显著性。p值是判断r值相关系数是否具有统计学意义，判定标准一般为0.05，如果p值小于0.05说明两者之间具有较强的相关性，即r值有意义。然后再看r值，|r|值越大，相关性越好。正数指正相关，即一个变量随着另一个变量增大而增大；负数指负相关，即一个变量随另一个变量的增大而减小。可以看出右侧上三角中的r值与左侧下三角中的r值是一样的，具有相同的统计学概念。Pearson相关系数的一点小补充Pearson主要是把两个序列看成两个变量，计算两个变量是否线性相关。Pearson公式如：$$ p_{XY} = \frac{cov(X,Y)}{\sigma _{X} \sigma _{Y}}=\frac{E(XY)-EXEY}{\sigma _{X} \sigma _{Y}} $$分子是协方差的计算公式，协方差我们知道，是计算两个变量的相关性，大于0是正相关，小于是负相关，相等不相关。除以分母相当于归一化到[-1,1]之间。所以，Pearson相关系数的计算结果也等于将数据矩阵进行标准化后再求协方差，此时求出的协方差就等于源数据矩阵中各列的相关性。代码演示：import pandas as pd # 引包 import seaborn as…

「Python」数据分析奇技淫巧
type: item
date: 2022-07-30T11:19:39+08:00
description: Matplotlib绘图相关中文与负数中负号-的显示：plt.rcParams['font.sans-serif'] = ['SimHei'] # 中文显示 plt.rcParams['axes.unicode_minus'] = False # 负号显示当同时绘制的两组数据差距过大时，可以使用：plt.yscale('log') # 对y轴进行log缩放，减小两组图视觉上的差异，更方便观察（★★★★★）进行对数转换，减小不同组数据之间的量级差异。处理前处理后数据透视表三个分组统计函数size()、sum()、count()三个统计函数，能分别统计分组数量、不同列的分组和、不同列的分组数量。举个栗子：import pandas as pd df = pd.DataFrame({ 'key1': ['a', 'a', 'b', 'b', 'a'], 'key2': ['one', 'two', 'one', 'two', 'one'], 'x': np.random.randn(5), 'y': np.random.randn(5)}) df Out[4]: key1 key2 x y 0 a one -0.343202 0.992670 1 a two -0.996222 -0.181044 2 b one 1.380729 1.091965 3 b two 0.520865 -1.400465 4 a one -0.635766 -1.154578count()首先是count()函数，这个函数真的就是数数的，类似于pd.Series.value_counts()。df.groupby('key1').count() # key1列下面有3个a，2个b Out[5]: key2 x y key1 a 3 3 3 b 2 2 2 df.groupby(['key1', 'key2']).count() # key1列和key2列的values共有四种随机组合：a-one、a-two、：b-one、b-two，然后数一数他们的个数。 Out[6]: x y key1 key2 a one 2 2 two 1 1 b one 1 1 two 1 1 df.key1.value_counts() # 等同于pd.Series.value_counts() Out[7]: a 3 b 2 Name: key1, dtype: int64​ 所以综上所述，分组聚合时，使用count()函数好像根本没有意义~sum()不同于无聊的count()函数，sum()函数在分组聚合中感觉还是有点用的：df.groupby('key1').sum() # 将df按照key1列的value分份儿，然后求和。 Out[8]: # x列对应key1=a的value和为-1.975191，y列对应key1=b的value和为-0.308500 x y key1 a -1.975191 -0.342953 b 1.901594 -0.308500 df.groupby(['key1', 'key2']).sum() # 将df按照key1、key2列value的随机组合分份儿，然后求和。 Out[9]: # x列对应key1=a、key2=one的value和为-0.978968，y列对应key1=b、key2=two的value和为-1.400465 x y key1 key2 a one -0.978968 -0.161908 two -0.996222 -0.181044 b one 1.380729 1.091965 two 0.520865 -1.400465size()size()函数，顾名思义，就是求大小，这个大小不是指数值上的大小，而是个数上的大小。df.groupby('key1').size() # 先看key1列有几个value，然后看每个value有几行。 Out[10]: key1 a 3 b 2 dtype: int64 df.groupby(['key1', 'key2']).size() # 先看key1列与key2value的随机组合有几个，然后看每个组合有几行。 Out[11]: key1 key2 a one 2 two 1 b one 1 two 1 dtype: int64修改行索引列名称的方法# 方法一 df.rename_axis('Index', inplace=True) # 方法二 df.index.name = 'Index'提取时间格式列中的月份In [8]: demo = pd.DataFrame({ ...: 'date': ['2022-07-23', '2021-07-23'] ...: }) …

「Python」用户消费行为分析
type: item
date: 2022-07-24T20:41:00+08:00
description: 预准备引包import numpy as np import pandas as pd import matplotlib.pyplot as plt绘图全局设置# 行内绘图 # % matplotlib inline plt.style.use('ggplot') # 绘图风格 plt.rcParams['font.sans-serif'] = ['SimHei'] # 显示中文字体读取数据并整体观察数据结构In [3]: col = ['user', 'date', 'product', 'amount'] ...: df_0 = pd.read_table('./data/CDNOW_master.txt', names=col, sep='\s+') # sep='\s+ 列于列之间的分隔为至少一个空格 ...: df = df_0.copy() ...: df Out[3]: user date product amount 0 1 19970101 1 11.77 1 2 19970112 1 12.00 2 2 19970112 5 77.00 3 3 19970102 2 20.76 4 3 19970330 2 20.76 ... In [4]: df.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 69659 entries, 0 to 69658 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user 69659 non-null int64 1 date 69659 non-null int64 2 product 69659 non-null int64 3 amount 69659 non-null float64 dtypes: float64(1), int64(3) memory usage: 2.1 MB分析：观察数据结构可得，数据表一共有69659行、4列，且无空行，其中4列包括user（用户索引列）、date（用户消费时间列）、product（购买产品数量列）、amount（购买总金额列）。数据的预处理观察date（用户消费时间列）可发现，其时间格式Pandas未能识别，需要手动将其转换成时间格式列（datetime），方便后续操作。In [5]: df['date'] = pd.to_datetime(df['date'], format='%Y%m%d')后续数据分析需要按月来操作，因此需要读取date（用户消费时间列）中的月份，In [6]: df['month'] = df['date'].astype('datetime64[M]')注意看这里这里从date（用户消费时间列）中获取月份的方式，并没有使用：In [7]: df['date'].dt.month为什么呢？使用.dt.month虽然可以获取月份，但是遇到多个年份中提取月份的话就会出现问题，做个简单小对比：In [8]: demo = pd.DataFrame({ ...: 'date': ['2022-07-23', '2021-07-23'] ...: }) ...: demo['date'] = pd.to_datetime(demo['date']) ...: demo['astype'] = demo['date'].astype('datetime64[M]') ...: demo['dt.month'] = demo['date'].dt.month ...: demo Out[8]: date astype dt.month 0 2022-07-23 2022-07-01 7 1 2021-07-23 2021-07-01 7由此可以看出，当遇到多个年份有相同月份的时候，使用.dt.month仅仅可以提取出来月份，但是具体是哪一年的7月，确提取不出来，因此其存在着弊端。数据分析月统计量分析按月份统计产品购买数量、消费金额、消费次数以及消费人数。df.pivot_table( index='month', aggfunc={ 'user': 'count', # 每个月的顾客数量（当同一个顾客下多次订单时，都按照新顾客统计） 'product': 'count', 'amount': 'sum' })注意：这种聚合分析方法存在的问题就是，如果一个用户一个月内有多条消费记录，那么其每次都会被当作一个新的消费者记录，即通过'user': 'count'聚合得到的是一个月所有消费记录的数量，并不是本月内…

「Python」矩阵、向量的循环遍历
type: item
date: 2022-06-26T15:43:00+08:00
description: 在Python中，我们可以使用map()函数对list对象中的每一个元素进行循环迭代操作，例如：In [1]: a = [i for i in range(10)] In [2]: a Out[2]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] In [3]: list(map(lambda x: x**2 ,a)) # 对list对象a中的每一个元素都进行计算平方值。map() 函数生成的是一个map对象，需要使用list()函数对其强制转换为list对象才可以。 Out[3]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 那么在Pandas操作中，有没有类似的功能可以实现对矩阵或者向量进行操作呢？当时是有的，这篇笔记来汇总下自己了解的几种方法。apply()在Pandas中，无论是矩阵（DataFrame）或者是向量（Series）对象都是有apply()方法的。对DataFrame对象使用该方法的话就是对矩阵中的每一行或者每一列进行遍历操作（通过axis参数来确定是行遍历还是列遍历）；对Series对象使用该方法的话，就是对Series中的每一个元素进行循环遍历操作。对DataFrame对象使用apply()方法：In [4]: import pandas as pd In [5]: df = pd.DataFrame({'a': [10, 20, 30], 'b': [20, 30, 40]}) In [6]: df Out[6]: a b 0 10 20 1 20 30 2 30 40 In [7]: df.apply(sum) # 对df中每一列的Series使用sum函数 Out[7]: a 60 b 90 dtype: int64 In [10]: df.apply(lambda s: s.min(), axis=1) # 对df中的每一行Series使用.min()方法，axis=1设置对df中的行进行操作 Out[10]: 0 10 1 20 2 30 dtype: int64对Series对象使用apply()方法：In [12]: type(df['a']) Out[12]: pandas.core.series.Series In [14]: df['a'].apply(lambda x: x * 20) Out[14]: 0 200 1 400 2 600 Name: a, dtype: int64当apply()配合有参数的函数时，也可以为函数传递参数：In [15]: def calc_n(x, n): ...: return x ** n ...: In [16]: df['a'].apply(calc_n, n=10) Out[16]: 0 10000000000 1 10240000000000 2 590490000000000 Name: a, dtype: int64applymap()矩阵（DataFrame）的applymap()方法可以对矩阵中每一个元素进行遍历迭代操作：In [18]: df.applymap(lambda x: x * 2) Out[18]: a b 0 20 40 1 40 60 2 60 80 In [19]: df*2 Out[19]: a b 0 20 40 1 40 60 2 60 80行、列的迭代除了对矩阵使用apply()方法进行迭代外，还可以.iteritems()、.iterrows()与.itertuples()方法进行行、列的迭代，以便进行更复杂的操作。.iteritems()列迭代每次取出的i是一个元组，在元组中，第[0]项是原来的列名称，第[1]列是由原来该列的元素构成的一个Series：In [20]: for i in df.iteritems(): ...: print(type(i), i) ...: <class 'tuple'> ('a', 0 10 1 20 2 30 Name: a, dtype: int64) <class 'tuple'> ('b', 0 20 1 30 2 40 Name: b, dtype: int64) In [21]: for i in df.iteritems(): ...: print(type(i[1]), i[1]) ...: <class 'pandas.core.series.Series'> 0 10 1 20 2 30 Name: a, dtype: int64 <class 'pandas.core.series.Series'> 0 20 1 30 2 40 Name: b, dtype: int64行迭代与列迭代的形式一…

「Python」Generator生成器
type: item
date: 2022-06-26T10:52:00+08:00
description: 产生需求任何一个功能的出现，必定是有其对应的需求的。使用Python获取斐波拉契数列，斐波拉契数列（Fibonacci）：除第一个数以外，所有的数字都是由前面两项相加得到。def fib(n): """ 获取斐波拉契数列 :param n: 需要获取自然数n前的所有斐波拉契数列中的数字 :return: result，以列表的形式返回数列 """ a, b = 0, 1 result = [a, b] while True: c = a + b if c <= n: result.append(c) a, b = b, c else: break return result a = fib(100) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]问题通过上面的函数我们可以获取数字n前面的所有斐波拉契数列，但是如果我使用函数fib()获取了100前的所有斐波拉契数列，但是我再使用的过程中却发现我只需要10以前的斐波拉契数列就行了。这个时候，就产生了内存浪费，制造了100前的所有斐波拉契数列，但是你实际需求只用了10以前的。抛出疑问能不能写出这样一个函数，就是我所产生的斐波拉契数列个数是小于等于100前的，但是至于我用到哪些，现在是还不确定的，但是我可以随用随造，不用10以后的斐波拉契数列的话，那么这个函数就不会制造后面的呢？解决办法这个时候，就用到了生成器。结合列表推导式，把其两端的[]换成()即可得到生成器，一个简单的举例：a = (i for i in range(3))与列表推导式不同的是，列表生成器在创建的时候直接生成了一个列表：[0, 1, 2]，而生成器则是创建了一个生成器对象，只有在后续调用的时候才会生成其中的元素。b = [i for i in range(3)] print(a) # <generator object <genexpr> at 0x000001A7767A5690> print(b) # [0, 1, 2]如何使用生成器呢？生成器每次使用都需要进行调用，且生成的值只能使用一次，之后就产生下一个元素：print(a.__next__()) # 0 print(a.__next__()) # 1 print(next(a)) # 2 print(a.__next__()) # 报错：StopIteration如果超出生成器中元素个数，就会报错。上述逐个调用生成器中的元素的话，如果超出则会报错，但是使用for循环的话就不会报错：for i in a: print(i) #0, 1, 2其实Python中很多地方都有生成器的使用场景，例如使用open()函数对文件执行逐行读取的时候，就是每次读取一行，不读就不取。yield关键字回到最初提出的问题，如何实现函数中也实现生成器的效果呢？这个时候就需要使用yield关键字。def fib(n): a, b = 0, 1 i = 0 while True: i += 1 print(f'开始第{i}次循环...') c = a + b if c <= n: yield c a, b = b, c else: break print(f'第{i}次循环结束...') # 正常函数在创建的时候就会运行完毕，但是加入yield关键字的函数，在创建的时候，运行到yield关键字的位置就会停止，并在第一次调用.__next__()方法的时候将yield关键字后面的变量返回出来，然后继续执行后续代码，直至再次遇见yield。 a = fib(100) # [1, 2, 3, 5, 8, 13, 21, 34, 55, 89] while True: try: print(a.__next__()) except Exception as e: print('已打印完所有元素...', e) break

「Python」列表推导式
type: item
date: 2022-06-24T10:14:00+08:00
description: 什么是列表推导式？当一个for循环中代码比较简单的时候，往往都可以写成列表推导式，这样代码更加简洁、高效。举个栗子：a = list(range(1, 11)) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # 智障版 for index, value in enumerate(a): # a[index] = value + 1 a[index] += 1 a # [2, 3, 4, 5, 6, 7, 8, 9, 10, 11] # map()函数 list(map(lambda x: x + 1, a)) # [3, 4, 5, 6, 7, 8, 9, 10, 11, 12] # 列表生成式 [i+1 for i in a] #[3, 4, 5, 6, 7, 8, 9, 10, 11, 12]配合if else的高级使用仅配合if语句：[i ** 2 for i in a if i % 2 == 0] Out[5]: [0, 4, 16, 36, 64, 100]配合if else语句：首先复习一个三元表达式：b = 2 c = 1 if b >= 3 else 0 c Out[8]: 0然后再来看：[i ** 2 if i % 2 == 0 else i ** 3 for i in a ] Out[9]: [0, 1, 4, 27, 16, 125, 36, 343, 64, 729, 100]注意看，这个时候if else语句的位置已经在for语句之前了。配合if elif else语句：if elif else这个语句其实是不能实现的，但是可以绕一下，通过if else (if else)来实现（不加括号也是可以的，但是为了看起来更容易理解就加上了）：d = 2 if b >= 3 else (0 if b >= 0 else None) d Out[12]: 0理解了上述语句之后，再来看列表推导式就不难了：[i ** 2 if i % 2 == 0 else (i ** 3 if i % 3 == 0 else None) for i in a] Out[13]: [0, None, 4, 27, 16, None, 36, None, 64, 729, 100]配合函数的高级使用当需要对for寻循环中的每一个i都需要进行比较复杂操作的时候，可以通过封装函数的方法来实现。lambda匿名函数：[(lambda x: x ** x)(i) for i in a] # 计算列表a中每一个x数的x次方 Out[15]: [1, 1, 4, 27, 256, 3125, 46656, 823543, 16777216, 387420489, 10000000000]配合lambda匿名函数加上if语句：[(lambda x: x ** x)(i) for i in a if i % 2 == 0] # 计算列表a中每一个x数的x次方 Out[16]: [1, 4, 256, 46656, 16777216, 10000000000]配合自定义函数：def calc(n): return n ** n [(lambda x: x ** 0)(i) if i % 2 == 0 else calc(i) for i in a] Out[19]: [1, 1, 1, 27, 1, 3125, 1, 823543, 1, 387420489, 1]除了使用列表推导式+函数的方法来对列表中的每一个元素进行操作，还有一种简便的方法就是使用map()函数。# map对象 map(lambda x: x ** 2, [i for i in a if i % 2 == 0]) Out[21]: # 配合lambda匿名函数 list(map(lambda x: x ** 2, [i for i in a if i % 2 == 0])) Out[22]: [0, 4, 16, 36, 64, 100] # 配合一般函数 list(map(calc, [i for i in a if i % 2 == 0])) Out[24]: [1, 4, 256, 46656, 16777216, 10000000000]需要注意的是，map()函数返回值是一个map对象，需要使用list()强制转化。这里有个小疑问：Pandas里的apply()函数可以为其函数传递参数，但是map()函数好像没有这个功能。import pandas as pd def calc_n(x, n): return x ** n a_s = pd.Series(a) a_s.apply(calc_n, n = 10) # 函数calc_的参数也可以通过a…

「Python」装饰器到底是个啥？
type: item
date: 2022-06-23T22:15:00+08:00
description: 今天在B站看了个视频，终于让我搞懂了什么是装饰器，解决了心里多年的一个困惑，记录下。产生的需求一个新闻门户网站，分为sports、news、user_center三个页面，想要实现用户必须登录才能看片的效果。源代码：user_info = { # 用户信息 'user_name': 'Yacan', 'password': '123' } def sports(): print("体育页面...") def news(): print("新闻页面...") def user_center(): print('用户中心...')简单粗暴的实现方法# 为用户增加一个is_login的属性，当用户登陆后，将其赋值为True user_info = { 'user_name': 'yacan', 'password': '123', 'is_login': False } # 增加一个check_login函数，用来检查用户的登录状态 def check_login(): name = input('请输入用户名：') password = input('请输入密码：') if name == user_info['user_name'] and password == user_info['password']: user_info['is_login'] = True print('登录成功~') else: print('账号或者密码错误，登陆失败！') # 在加载每一个板块内容前都先执行登录函数 def sports(): check_login() print("体育页面...") def news(): check_login() print("新闻页面...") def user_center(): check_login() print('用户中心...')问题：简单粗暴的方式违背了代码的“开放-封闭”原则，即当我们增加新功能时，要尽量地不在原代码上产生改动，尽量把所有的改动都写到函数外。升级版如果我们可以将需求函数sports()、news()、user_center()放入check_login()函数来执行的话，就可以不在源代码上进行修改，从而把全部的新增代码都放到了外面，通过Python函数的高级用法——把一个函数作为参数传入另一个函数内：def check_login(fun): name = input('请输入用户名：') password = input('请输入密码：') if name == user_info['user_name'] and password == user_info['password']: user_info['is_login'] = True print('登录成功~') fun() # 执行传入的函数 else: print('账号或者密码错误，登陆失败！') sports = check_login(sports) sports() news = check_login(news) news() user_center = check_login(user_center) user_center()问题：在执行sports = check_login(sports)的时候，由于check_login(fun)没有return（返回值），导致sports = None，后面执行sports()的时候，就是None()，直接报错。小知识点在Python中定义一个函数def fun()后，直接调用fun返回的是函数的内存地址，调用fun()返回的是函数的return值。def fun(): return 1 print(fun(), ' | ', fun) # 1 | <function fun at 0x000001FBF182EB90>完善想要解决上述问题，就是想办法让执行sports = check_login(sports)的时候返回的是函数的内存ID，就可以解决这个问题。这个时候就用到了一个伟大的概念——闭包。def check_login(fun): def inner(): # 所谓闭包，就是在函数里面加上一个嵌套函数，然后将内部嵌套函数返回出去。 name = input('请输入用户名：') password = input('请输入密码：') if name == user_info['user_name'] and password == user_info['password']: user_info['is_login'] = True print('登录成功~') fun() else: print('账号或者密码错误，登陆失败！') retur…

数据中心化与标准化
type: item
date: 2022-05-01T11:57:00+08:00
description: 举个🌰来说明通过一个例子来说明什么是中心化与标准化，并且如何进行标准化与中心化。1、Python包的加载与画图设置（不懂可以不看）：# 包的加载 import pandas as pd import numpy as np import seaborn as sns import matplotlib.pyplot as plt import palettable #python颜色库 # 用于显示中文 plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False2、加载数据：不懂代码的话可以理解为这里是读取一个表格数据。# 这里做一个对矩阵进行标准化与中心化的对比，阐述其意义 state_data_0 = pd.read_csv('./state.csv') state_data_0.set_index('Unnamed: 0', inplace=True) state_data_0.index._name = 'state' state_data = state_data_0.copy() state_data这个数据表是美国几个州的统计数据，每一行代表一个州，每一列分别是人口（Population）、收入（Income）、受教育程度（Illiteracy）、平均寿命（Life Exp）等等。表格数据如图所示3、使用表格数据画图：plt.figure(dpi=120) # 在进行标准化与中心化处理之前绘制热图，由于不同列的数据差距过大，在绘制热图的时候就会导致反应出来的效果不好（例如：某一列的数据都在100~900，而另一列的数据都在10~99） # sns.heatmap(state_data) sns.heatmap(data=state_data, cmap=sns.cubehelix_palette(as_cmap=True), cbar=True, cbar_kws={'label': 'ColorBar', #color bar的名称 'orientation': 'vertical', #color bar的方向设置，默认为'vertical'，可水平显示'horizontal' # "ticks": np.arange(4.5, 8, 0.5), #color bar中刻度值范围和间隔 # "format": "%.2f", #格式化输出color bar中刻度值 "pad": 0.05, #color bar与热图之间距离，距离变大热图会被压缩 }, # annot=True, #默认为False，当为True时，在每个格子写入data中数据 # fmt=".2f",#设置每个格子中数据的格式，参考之前的文章，此处保留两位小数 # annot_kws={'size':4,'weight':'normal', 'color':'blue'},#设置格子中数据的大小、粗细、颜色 linewidths=1, #格子与格子，默认为0 # linecolor='red', #每个格子边框颜色,默认为白色 ) plt.title('未经过中心化与标准化的数据')出图4、遇到了问题：如上图所示，使用表格数据绘制了一张热图，但是发现由于表格中的数据相差过大（Population列都在千、万级别，Forst都在10~1000级别）导致绘制出来的图并不能很好地表达出来一定的区分度。这个时候就在想有没有一种方法能够解决这个问题呢？——数据的标准化与中心化，也被称为均值方差归一化（Standardization），把所有数据归一到均值为0方差为1的分布中，适用于数据分布没有明显边界的情况中。标准化与中心化其实就是对一组数据求平均值和方差，然后计算：$$ 处理后的结果=\frac{(源数据-源数据平均值)}{源数据的方差} $$Python代码实现：def norm_(pd_raw): """ 定义一个可以对DataFrame进行中心化与标准化的函数 :param x: DataFrame :return: 经过标准化的DataFrame """ pd_mean = np.mean(pd_raw, 0) # 求DataFrame每一列的平均值 pd_std = np.std(pd_raw, 0) # 求DataFrame每一列的标准差 return (pd_raw - pd_mean) / pd_std上面这个函数就是定义一个可以用于将数据（Python中的DataFrame对象）进行标准化与中心化的函数，不懂代码的话可以理解为这一步就是如何将数据进行标准化与中心化。5、对源数据进行标准化与中心化，然后重新出图：sta…