data_analysis.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. # !usr/bin/env python
  2. # -*- coding:utf-8 _*-
  3. """
  4. @Author:Lijiaxing
  5. @File:data_analysis.py
  6. @Time:2023/4/24 15:16
  7. """
  8. import os.path
  9. import pandas as pd
  10. # from mpl_toolkits.basemap import Basemap
  11. from scipy.signal import savgol_filter
  12. import numpy as np
  13. import matplotlib.pyplot as plt
  14. from scipy.cluster.hierarchy import dendrogram, linkage, fcluster
  15. from sklearn.metrics import silhouette_samples, silhouette_score
  16. def paint_others(y):
  17. """ 绘制其他数据 """
  18. plt.plot([j for j in range(y)], y)
  19. # 添加标题和标签
  20. plt.xlabel('x')
  21. plt.ylabel('y')
  22. # 显示图形
  23. plt.show()
  24. def compute_cos_similarity(a, b):
  25. """
  26. 计算两个向量的余弦相似度
  27. :param a: 向量a
  28. :param b: 向量b
  29. :return: 余弦相似度值
  30. """
  31. return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
  32. def compute_pearsonr(a):
  33. """
  34. 计算数据皮尔逊相关系数并返回相似度矩阵
  35. :param a: 数据格式为n*m的矩阵,n为数据个数,m为数据维度
  36. :return: 返回相似度矩阵,数据格式为n*n的矩阵
  37. """
  38. return np.corrcoef(a)
  39. def compute_distance(a, b):
  40. """
  41. 计算两个向量的欧式距离
  42. :param a:
  43. :param b:
  44. :return: 返回两个向量的欧式距离
  45. """
  46. return np.linalg.norm(a - b)
  47. def similarity_score(turbine_diff, threshold=0.5):
  48. """
  49. 使用余弦相似度计算相似度分数并返回相似度大于阈值的index矩阵
  50. :param turbine_diff: 需要计算相似的矩阵,数据格式n*m,n为数据条数,m为数据维数
  51. :param threshold: 相似度阈值
  52. :return: 返回相似计算后的矩阵
  53. """
  54. similarity = {i: [] for i in range(49)}
  55. similarity_index = {i: [] for i in range(49)}
  56. for turbine_i in range(49):
  57. for turbine_j in range(49):
  58. cos_similarity = compute_cos_similarity(turbine_diff[turbine_i], turbine_diff[turbine_j])
  59. similarity[turbine_i].append(cos_similarity)
  60. if cos_similarity > threshold:
  61. similarity_index[turbine_i].append(turbine_j)
  62. return similarity_index
  63. def hierarchical_clustering(data, threshold, similarity_func):
  64. """
  65. 层次聚类,使用工具包scipy.cluster.hierarchy中的linkage和fcluster函数进行层次聚类
  66. :param data: 二维数据,格式为n*m的矩阵,n为数据个数,m为数据维度
  67. :param threshold: 阈值,当两个数据的距离小于阈值时,将两个数据归为一类,阈值为根据相似度矩阵层次聚类后的类别距离阈值,可根据需求进行调整,可大于1
  68. :param similarity_func: 相似度计算函数,用于计算两个数据的相似度,可以进行替换,若替换为计算距离的函数需对内部进行修改
  69. :return: 返回聚类结果,格式为n*1的矩阵,n为数据个数,每个数据的值为该数据所属的类别
  70. """
  71. # 计算数据的相似度矩阵
  72. similarity_matrix = similarity_func(data)
  73. # 计算数据的距离矩阵
  74. distance_matrix = 1 - similarity_matrix
  75. # 进行层次聚类返回聚类结果
  76. Z = linkage(distance_matrix, method='ward')
  77. # 根据相似度阈值获取聚类结果
  78. clusters = fcluster(Z, t=threshold, criterion='distance')
  79. print(clusters)
  80. # 画出层次聚类树形结构
  81. # fig = plt.figure(figsize=(5, 3))
  82. # dn = dendrogram(Z)
  83. # plt.show()
  84. # clusters[42] = 1
  85. silhouette = silhouette_samples(np.abs(distance_matrix), clusters, metric='euclidean')
  86. silhouette1 = silhouette_score(np.abs(distance_matrix), clusters, metric='euclidean')
  87. print(f"平均轮廓系数为:{silhouette1}, 单个样本的轮廓系数:{silhouette}")
  88. return clusters
  89. class DataAnalysis(object):
  90. """
  91. 数据分析类
  92. """
  93. def __init__(self, data_length, data_start, data_end):
  94. """
  95. 初始化
  96. :param data_length: 分析数据段长度
  97. :param data_start: 分析数据段开始位置
  98. :param data_end: 分析数据段结束位置
  99. """
  100. # 原始风机功率数据傅里叶变换滤波后的数据
  101. self.ori_turbine_power = None
  102. self.ori_turbine_fft = None
  103. # 原始风机功率数据片段
  104. self.ori_turbine_pic = None
  105. # 聚类结果
  106. self.cluster = None
  107. # 风机功率差分平滑后的结果
  108. self.smooth_turbine_diff = None
  109. # 风机功率差分变化情况
  110. self.diff_change = None
  111. # 风机功率差分
  112. self.turbine_diff = None
  113. # 全部风机数据
  114. self.turbine = None
  115. # 风机的标号顺序
  116. self.turbine_id = [x for x in range(76, 151, 1)]
  117. self.c_names = ['G01', 'G02', 'G03', 'G04', 'G05', 'G06', 'G07', 'G08', 'G09', 'G10'] + ['G' + str(x) for x in range(11, 76)]
  118. # b1b4 = [142, 143, 144, 145]
  119. # self.turbine_id = [id for id in self.turbine_id if id not in b1b4]
  120. # 风机功率数据15分钟级别
  121. self.power_15min = None
  122. # 风机经纬度信息
  123. self.info = None
  124. # 使用数据长度
  125. self.data_length = data_length
  126. # 使用数据开始位置
  127. self.data_start = data_start
  128. # 使用数据结束位置
  129. self.data_end = data_end
  130. # 导入数据
  131. self.turbine_path = '../../cluster/260/turbine-{}.csv'
  132. self.load_data(normalize=False)
  133. # 计算风机功率差分
  134. self.compute_turbine_diff()
  135. def load_data(self, normalize=False):
  136. """
  137. 加载数据
  138. :return:
  139. """
  140. self.turbine, turbines = {}, []
  141. for i in self.turbine_id:
  142. self.turbine[i] = pd.read_csv(self.turbine_path.format(i)).reset_index(drop=True)
  143. if normalize is True:
  144. self.normalize()
  145. def normalize(self):
  146. turbines = [self.turbine[i].values[:, 1:].astype(np.float32) for i in self.turbine_id]
  147. turbines = np.vstack(turbines)
  148. mean, std = np.mean(turbines, axis=0), np.std(turbines, axis=0)
  149. for i in self.turbine_id:
  150. c_time = self.turbine[i]['C_TIME']
  151. self.turbine[i] = (self.turbine[i].iloc[:, 1:] - mean) / std
  152. self.turbine[i].insert(loc=0, column='C_TIME', value=c_time)
  153. return self.turbine
  154. def compute_turbine_diff(self):
  155. """
  156. 计算风机功率差分
  157. :return:
  158. """
  159. turbine_diff = []
  160. ori_turbine_pic = []
  161. ori_turbine_power = []
  162. for turbine_i in self.turbine_id:
  163. ori = np.array(self.turbine[turbine_i]['C_WS'].values[self.data_start:self.data_end + 1])
  164. diff_array = np.diff(ori)
  165. smoothness_value = np.std(diff_array)
  166. print("turbine-{}的平滑度是:{}".format(turbine_i, round(smoothness_value, 2)))
  167. turbine_diff.append(diff_array)
  168. ori_turbine_pic.append(self.turbine[turbine_i]['C_WS'].values[self.data_start:self.data_end + 1])
  169. ori_turbine_power.append(self.turbine[turbine_i]['C_ACTIVE_POWER'].values[self.data_start:self.data_end + 1])
  170. self.ori_turbine_power = ori_turbine_power # 风机功率
  171. self.ori_turbine_pic = ori_turbine_pic # 风机风速
  172. self.turbine_diff = turbine_diff # 风机风速差分
  173. diff_change = []
  174. for diff_i in turbine_diff:
  175. single_diff_change = []
  176. for diff_i_i in diff_i:
  177. if diff_i_i > 0:
  178. single_diff_change.append(1)
  179. elif diff_i_i < 0:
  180. single_diff_change.append(-1)
  181. else:
  182. single_diff_change.append(0)
  183. diff_change.append(single_diff_change)
  184. self.diff_change = diff_change # 风机风速差分1 -1 0 标值化
  185. # 风机风速傅里叶变换
  186. self.ori_turbine_fft = [self.turbine_fft(i + 1) for i in range(len(self.ori_turbine_pic))]
  187. # 风机风速差分平滑化
  188. self.turbine_smooth(window_size=21)
  189. def mapping_turbines(self):
  190. turbine_clus = {}
  191. id_names = {id: self.c_names[x] for x, id in enumerate(self.turbine_id)}
  192. import pickle
  193. for a, b in zip(self.turbine_id, self.cluster):
  194. print("风机编号:{},类别:{}".format(a, b))
  195. turbine_clus.setdefault(b, []).append(id_names[a])
  196. path = os.path.join(os.path.dirname(self.turbine_path), 'turbine_cls.pickle')
  197. with open(path, 'wb') as file:
  198. pickle.dump(turbine_clus, file)
  199. def turbine_smooth(self, window_size=50):
  200. """
  201. 使用滑动平均平滑数据。
  202. 参数:
  203. data -- 需要平滑的数据,numpy数组类型
  204. window_size -- 滑动窗口大小,整数类型
  205. 返回值:
  206. smooth_data -- 平滑后的数据,numpy数组类型
  207. """
  208. # weights = np.repeat(1.0, window_size) / window_size
  209. smooth_data = []
  210. for turbine_diff_i in self.turbine_diff:
  211. smooth_y = savgol_filter(turbine_diff_i, window_length=window_size, polyorder=3)
  212. smooth_data.append(smooth_y)
  213. # smooth_data.append(np.convolve(turbine_diff_i, weights, 'valid'))
  214. self.smooth_turbine_diff = smooth_data
  215. def paint_turbine_k(self, k):
  216. """
  217. 绘制第k聚类的风机数据折线图
  218. :param k:
  219. :return:
  220. """
  221. pic_label = []
  222. y = []
  223. plt.figure(figsize=(20, 10))
  224. cmap = plt.get_cmap('viridis')
  225. for i, item in enumerate(self.cluster):
  226. if item == k:
  227. pic_label.append('turbine-' + str(self.turbine_id[i]))
  228. y.append(self.ori_turbine_fft[i])
  229. for i in range(len(y)):
  230. color = cmap(i / 10)
  231. plt.plot([j for j in range(len(y[i]))], y[i], color=color, label=pic_label[i])
  232. # 添加标签和标题
  233. plt.xlabel('x')
  234. plt.ylabel('y')
  235. plt.title('Cluster {}'.format(k))
  236. # 添加图例
  237. plt.legend()
  238. # 显示图形
  239. plt.savefig('analysis_img/cluster_{}.png'.format(k))
  240. plt.show()
  241. def turbine_fft(self, k):
  242. """
  243. 对第k台原始风机数据进行傅里叶变换,并绘制变换前后曲线
  244. :param k: 数据读入时的风机顺序index,从1开始
  245. :return: 傅里叶变换清洗后的数据,数据格式
  246. """
  247. y = self.ori_turbine_pic
  248. t = np.linspace(0, 1, self.data_length)
  249. signal = y[k - 1]
  250. # 进行傅里叶变换
  251. freq = np.fft.fftfreq(len(signal), t[1] - t[0])
  252. spectrum = np.fft.fft(signal)
  253. spectrum_abs = np.abs(spectrum)
  254. threshold = np.percentile(spectrum_abs, 98)
  255. indices = spectrum_abs > threshold
  256. spectrum_clean = indices * spectrum
  257. # 进行傅里叶逆变换
  258. signal_clean = np.fft.ifft(spectrum_clean)
  259. # plt.figure(figsize=(20, 10))
  260. #
  261. # # 绘制时域信号
  262. # plt.subplot(4, 1, 1)
  263. # plt.plot(t, signal)
  264. # plt.title(self.turbine_id[k-1])
  265. #
  266. # # 绘制频域信号
  267. # plt.subplot(4, 1, 2)
  268. # plt.plot(freq, np.abs(spectrum))
  269. #
  270. # # 绘制过滤后的频域信号
  271. # plt.subplot(4, 1, 3)
  272. # plt.plot(freq, np.abs(spectrum_clean))
  273. #
  274. # # 绘制过滤后的时域信号
  275. # plt.subplot(4, 1, 4)
  276. # plt.plot(t, signal_clean)
  277. #
  278. # plt.savefig('analysis_img/fft/{}_turbine_fft.png'.format(self.turbine_id[k-1]))
  279. # plt.show()
  280. return signal_clean
  281. def paint_double(self, i, j):
  282. """
  283. 绘制两台风机的数据变换对比
  284. :param i: 风机数据读入时数据编号,从1开始
  285. :param j: 风机数据读入时数据编号,从1开始
  286. :return:
  287. """
  288. y = self.ori_turbine_fft
  289. x = [index for index in range(self.data_length)]
  290. data_i = y[i - 1]
  291. data_j = y[j - 1]
  292. plt.figure(figsize=(20, 10))
  293. plt.plot(x, data_i, label='turbine {}'.format(self.turbine_id[i - 1]), linestyle='solid')
  294. plt.plot(x, data_j, label='turbine {}'.format(self.turbine_id[j - 1]), linestyle='dashed')
  295. plt.title('{} and {}'.format(i, j))
  296. plt.legend()
  297. plt.savefig('analysis_img/{}_{}_turbine.png'.format(self.turbine_id[i - 1], self.turbine_id[j - 1]))
  298. plt.show()
  299. def process_ori_data(self):
  300. """
  301. 对原始数据进行处理,聚类和绘图
  302. :return:
  303. """
  304. self.turbine_clusters(self.ori_turbine_fft)
  305. # self.paint_turbine()
  306. def turbine_clusters(self, data=None):
  307. """
  308. 风机数据聚类,聚类信息保存在self.cluster中
  309. :param data: 默认为空,也可以使用其他数据聚类,并体现在绘图中,
  310. 数据格式:二维数据n*m,n为数据条数,m为每条数据维数
  311. :return: None
  312. """
  313. if data is None:
  314. cluster = hierarchical_clustering(self.turbine_diff, threshold=1.4, similarity_func=compute_pearsonr) # 层次聚类
  315. else:
  316. cluster = hierarchical_clustering(data, threshold=0.6, similarity_func=compute_pearsonr)
  317. self.cluster = cluster
  318. # 在这里保存cluster变量
  319. # from cluster_analysis import cluster_power_list_file, cluster_power_list_folder
  320. #
  321. # output_path = '../data-process/data/cluster_power/'
  322. # cluster_power_list_file(self.cluster, self.turbine_id,
  323. # input_path='../data-process/data/output_filtered_csv_files/', output_path=output_path)
  324. # cluster_power_list_folder(self.cluster, self.turbine_id, input_path='../data-process/data/continuous_data/',
  325. # output_path=output_path)
  326. if __name__ == '__main__':
  327. # import pickle
  328. # with open('./turbine_dict.pickle', 'rb') as f:
  329. # turbine_dict = pickle.load(f)
  330. # number, dt = 0, []
  331. # for key, turbine in turbine_dict.items():
  332. # if number == 0:
  333. # dt = turbine['时间']
  334. # number += 1
  335. # else:
  336. # dt = pd.to_datetime(list(set(dt) & set(turbine['时间'])))
  337. # for key, turbine in turbine_dict.items():
  338. # turbine_dict[key] = turbine[turbine['时间'].isin(dt)]
  339. # with open('./turbine_dict_common.pickle', 'wb') as f:
  340. # pickle.dump(turbine_dict, f)
  341. data_analysis = DataAnalysis(data_length=8549,
  342. data_start=0,
  343. data_end=8540)
  344. data_analysis.process_ori_data()
  345. data_analysis.mapping_turbines()
  346. # data_analysis.paint_double(1, 56)
  347. # data_analysis.paint_turbine()