data_analysis.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  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 linkage, fcluster
  15. def paint_others(y):
  16. """ 绘制其他数据 """
  17. plt.plot([j for j in range(y)], y)
  18. # 添加标题和标签
  19. plt.xlabel('x')
  20. plt.ylabel('y')
  21. # 显示图形
  22. plt.show()
  23. def compute_cos_similarity(a, b):
  24. """
  25. 计算两个向量的余弦相似度
  26. :param a: 向量a
  27. :param b: 向量b
  28. :return: 余弦相似度值
  29. """
  30. return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
  31. def compute_pearsonr(a):
  32. """
  33. 计算数据皮尔逊相关系数并返回相似度矩阵
  34. :param a: 数据格式为n*m的矩阵,n为数据个数,m为数据维度
  35. :return: 返回相似度矩阵,数据格式为n*n的矩阵
  36. """
  37. return np.corrcoef(a)
  38. def compute_distance(a, b):
  39. """
  40. 计算两个向量的欧式距离
  41. :param a:
  42. :param b:
  43. :return: 返回两个向量的欧式距离
  44. """
  45. return np.linalg.norm(a - b)
  46. def hierarchical_clustering(data, threshold, similarity_func):
  47. """
  48. 层次聚类,使用工具包scipy.cluster.hierarchy中的linkage和fcluster函数进行层次聚类
  49. :param data: 二维数据,格式为n*m的矩阵,n为数据个数,m为数据维度
  50. :param threshold: 阈值,当两个数据的距离小于阈值时,将两个数据归为一类,阈值为根据相似度矩阵层次聚类后的类别距离阈值,可根据需求进行调整,可大于1
  51. :param similarity_func: 相似度计算函数,用于计算两个数据的相似度,可以进行替换,若替换为计算距离的函数需对内部进行修改
  52. :return: 返回聚类结果,格式为n*1的矩阵,n为数据个数,每个数据的值为该数据所属的类别
  53. """
  54. # 计算数据的相似度矩阵
  55. similarity_matrix = similarity_func(data)
  56. # 计算数据的距离矩阵
  57. distance_matrix = 1 - similarity_matrix
  58. # 进行层次聚类返回聚类结果
  59. Z = linkage(distance_matrix, method='ward')
  60. # 根据相似度阈值获取聚类结果
  61. clusters = fcluster(Z, t=threshold, criterion='distance')
  62. return clusters
  63. class DataAnalysis:
  64. """
  65. 数据分析类
  66. """
  67. def __init__(self, data_length, data_start, data_end):
  68. """
  69. 初始化
  70. :param data_length: 分析数据段长度
  71. :param data_start: 分析数据段开始位置
  72. :param data_end: 分析数据段结束位置
  73. """
  74. # 原始风机功率数据傅里叶变换滤波后的数据
  75. self.ori_turbine_fft = None
  76. # 原始风机功率数据片段
  77. self.ori_turbine_pic = None
  78. # 聚类结果
  79. self.cluster = None
  80. # 风机功率差分平滑后的结果
  81. self.smooth_turbine_diff = None
  82. # 风机功率差分变化情况
  83. self.diff_change = None
  84. # 风机功率差分
  85. self.turbine_diff = None
  86. # 全部风机数据
  87. self.turbine = None
  88. # 风机的标号顺序
  89. self.turbine_id = list(range(102, 162))
  90. self.turbine_id.remove(144)
  91. # 风机功率数据15分钟级别
  92. self.power_15min = None
  93. # 风机经纬度信息
  94. self.info = None
  95. # 使用数据长度
  96. self.data_length = data_length
  97. # 使用数据开始位置
  98. self.data_start = data_start
  99. # 使用数据结束位置
  100. self.data_end = data_end
  101. # 导入数据
  102. self.load_data()
  103. # 计算风机功率差分
  104. self.compute_turbine_diff()
  105. def load_data(self):
  106. """
  107. 加载数据
  108. :return:
  109. """
  110. self.info = pd.read_csv('../data-process/data/风机信息.csv', encoding='utf-8')
  111. # power_15min = pd.read_csv('../data/power_15min.csv')
  112. # for i in range(len(power_15min)):
  113. # if power_15min.loc[i, 'C_REAL_VALUE'] == -9999:
  114. # # 方便在曲线中看出缺失数据位置
  115. # power_15min.loc[i, 'C_REAL_VALUE'] = -34.56789
  116. # self.power_15min = power_15min
  117. turbine_path = '../data-process/data/output_filtered_csv_files/turbine-{}.csv'
  118. self.turbine = {}
  119. for i in self.turbine_id:
  120. self.turbine[i] = pd.read_csv(turbine_path.format(i))[20:].reset_index(drop=True)
  121. def compute_turbine_diff(self):
  122. """
  123. 计算风机功率差分
  124. :return:
  125. """
  126. turbine_diff = []
  127. ori_turbine_pic = []
  128. for turbine_i in self.turbine_id:
  129. diff_array = np.diff(
  130. np.array(self.turbine[turbine_i]['C_ACTIVE_POWER'].values[self.data_start:self.data_end+1]))
  131. turbine_diff.append(diff_array)
  132. ori_turbine_pic.append(self.turbine[turbine_i]['C_ACTIVE_POWER'].values[self.data_start:self.data_end])
  133. self.ori_turbine_pic = ori_turbine_pic
  134. self.turbine_diff = turbine_diff
  135. diff_change = []
  136. for diff_i in turbine_diff:
  137. single_diff_change = []
  138. for diff_i_i in diff_i:
  139. if diff_i_i > 0:
  140. single_diff_change.append(1)
  141. elif diff_i_i < 0:
  142. single_diff_change.append(-1)
  143. else:
  144. single_diff_change.append(0)
  145. diff_change.append(single_diff_change)
  146. self.diff_change = diff_change
  147. self.ori_turbine_fft = [self.turbine_fft(i + 1) for i in range(len(self.ori_turbine_pic))]
  148. # 平滑
  149. self.turbine_smooth(window_size=21)
  150. def paint_map(self):
  151. """
  152. 绘制经纬度地图
  153. :return:
  154. """
  155. lats = self.info['纬度'].values
  156. lons = self.info['经度'].values
  157. map = Basemap()
  158. # 绘制海岸线和国家边界
  159. map.drawcoastlines()
  160. map.drawcountries()
  161. # 绘制经纬度坐标
  162. map.drawmeridians(range(0, 360, 30))
  163. map.drawparallels(range(-90, 90, 30))
  164. # 绘制点
  165. x, y = map(lons, lats)
  166. map.plot(x, y, 'bo', markersize=10)
  167. # 显示图表
  168. plt.show()
  169. def paint_power15min(self):
  170. """
  171. 绘制15分钟功率曲线
  172. :return:
  173. """
  174. plt.plot(self.power_15min['C_REAL_VALUE'])
  175. # 设置图表标题和轴标签
  176. plt.title('Data Time Change Curve')
  177. plt.xlabel('Date')
  178. plt.ylabel('Value')
  179. # 显示图表
  180. plt.show()
  181. def paint_lats_lons(self):
  182. """
  183. 绘制经纬度图
  184. :return:
  185. """
  186. x = self.info['纬度'].values
  187. y = self.info['经度'].values
  188. # 绘制散点图
  189. fig, ax = plt.subplots()
  190. plt.scatter(x, y)
  191. for i, txt in enumerate(self.info['id'].values):
  192. ax.annotate(txt, (x[i], y[i]))
  193. # 设置图表标题和轴标签
  194. plt.xlabel('lats')
  195. plt.ylabel('lons')
  196. # 显示图表
  197. plt.show()
  198. def similarity_score(self, turbine_diff, threshold=0.5):
  199. """
  200. 使用余弦相似度计算相似度分数并返回相似度大于阈值的index矩阵
  201. :param turbine_diff: 需要计算相似的矩阵,数据格式n*m,n为数据条数,m为数据维数
  202. :param threshold: 相似度阈值
  203. :return: 返回相似计算后的矩阵
  204. """
  205. similarity = {i: [] for i in range(49)}
  206. similarity_index = {i: [] for i in range(49)}
  207. for turbine_i in range(49):
  208. for turbine_j in range(49):
  209. cos_similarity = compute_cos_similarity(turbine_diff[turbine_i], turbine_diff[turbine_j])
  210. similarity[turbine_i].append(cos_similarity)
  211. if cos_similarity > threshold:
  212. similarity_index[turbine_i].append(turbine_j)
  213. return similarity_index
  214. def paint_turbine(self, paint_default=True):
  215. """
  216. 绘制风机地理位置图
  217. :param paint_default:默认True,绘制聚类后每个类别的数据折线图
  218. :return: None
  219. """
  220. # y = self.info['纬度'].values
  221. # x = self.info['经度'].values
  222. #
  223. # fig, ax = plt.subplots(figsize=(15, 15))
  224. #
  225. # plt.scatter(x, y, c=self.cluster)
  226. # for i, txt in enumerate(self.info['C_ID'].values):
  227. # ax.annotate(txt, (x[i], y[i]))
  228. # 设置图表标题和轴标签
  229. # plt.xlabel('lons')
  230. # plt.ylabel('lats')
  231. # plt.legend()
  232. #
  233. # # 显示图表
  234. # plt.savefig('analysis_img/turbine_cluster.png')
  235. # plt.show()
  236. if paint_default:
  237. for i in range(max(self.cluster)):
  238. self.paint_turbine_k(i + 1)
  239. def turbine_smooth(self, window_size=50):
  240. """
  241. 使用滑动平均平滑数据。
  242. 参数:
  243. data -- 需要平滑的数据,numpy数组类型
  244. window_size -- 滑动窗口大小,整数类型
  245. 返回值:
  246. smooth_data -- 平滑后的数据,numpy数组类型
  247. """
  248. # weights = np.repeat(1.0, window_size) / window_size
  249. smooth_data = []
  250. for turbine_diff_i in self.turbine_diff:
  251. smooth_y = savgol_filter(turbine_diff_i, window_length=window_size, polyorder=3)
  252. smooth_data.append(smooth_y)
  253. # smooth_data.append(np.convolve(turbine_diff_i, weights, 'valid'))
  254. self.smooth_turbine_diff = smooth_data
  255. def paint_turbine_k(self, k):
  256. """
  257. 绘制第k聚类的风机数据折线图
  258. :param k:
  259. :return:
  260. """
  261. pic_label = []
  262. y = []
  263. plt.figure(figsize=(20, 10))
  264. cmap = plt.get_cmap('viridis')
  265. for i, item in enumerate(self.cluster):
  266. if item == k:
  267. pic_label.append('turbine-'+str(self.turbine_id[i]))
  268. y.append(self.ori_turbine_fft[i])
  269. for i in range(len(y)):
  270. color = cmap(i / 10)
  271. plt.plot([j for j in range(len(y[i]))], y[i], color=color, label=pic_label[i])
  272. # 添加标签和标题
  273. plt.xlabel('x')
  274. plt.ylabel('y')
  275. plt.title('Cluster {}'.format(k))
  276. # 添加图例
  277. plt.legend()
  278. # 显示图形
  279. plt.savefig('analysis_img/cluster/cluster_{}.png'.format(k))
  280. plt.show()
  281. def turbine_fft(self, k):
  282. """
  283. 对第k台原始风机数据进行傅里叶变换,并绘制变换前后曲线
  284. :param k: 数据读入时的风机顺序index,从1开始
  285. :return: 傅里叶变换清洗后的数据,数据格式
  286. """
  287. y = self.ori_turbine_pic
  288. t = np.linspace(0, 1, self.data_length)
  289. signal = y[k - 1]
  290. # 进行傅里叶变换
  291. freq = np.fft.fftfreq(len(signal), t[1] - t[0])
  292. spectrum = np.fft.fft(signal)
  293. spectrum_abs = np.abs(spectrum)
  294. threshold = np.percentile(spectrum_abs, 98)
  295. indices = spectrum_abs > threshold
  296. spectrum_clean = indices * spectrum
  297. # 进行傅里叶逆变换
  298. signal_clean = np.fft.ifft(spectrum_clean)
  299. # plt.figure(figsize=(20, 10))
  300. #
  301. # # 绘制时域信号
  302. # plt.subplot(4, 1, 1)
  303. # plt.plot(t, signal)
  304. # plt.title(k)
  305. #
  306. # # 绘制频域信号
  307. # plt.subplot(4, 1, 2)
  308. # plt.plot(freq, np.abs(spectrum))
  309. #
  310. # # 绘制过滤后的频域信号
  311. # plt.subplot(4, 1, 3)
  312. # plt.plot(freq, np.abs(spectrum_clean))
  313. #
  314. # # 绘制过滤后的时域信号
  315. # plt.subplot(4, 1, 4)
  316. # plt.plot(t, signal_clean)
  317. #
  318. # plt.savefig('analysis_img/fft/{}_turbine_fft.png'.format(k))
  319. # plt.show()
  320. return signal_clean
  321. def paint_double(self, i, j):
  322. """
  323. 绘制两台风机的数据变换对比
  324. :param i: 风机数据读入时数据编号,从1开始
  325. :param j: 风机数据读入时数据编号,从1开始
  326. :return:
  327. """
  328. y = self.ori_turbine_fft
  329. x = [index for index in range(self.data_length)]
  330. data_i = y[i - 1]
  331. data_j = y[j - 1]
  332. plt.figure(figsize=(20, 10))
  333. plt.plot(x, data_i, label='turbine {}'.format(self.turbine_id[i-1]), linestyle='solid')
  334. plt.plot(x, data_j, label='turbine {}'.format(self.turbine_id[j-1]), linestyle='dashed')
  335. plt.title('{} and {}'.format(i, j))
  336. plt.legend()
  337. plt.savefig('analysis_img/{}_{}_turbine.png'.format(self.turbine_id[i-1], self.turbine_id[j-1]))
  338. plt.show()
  339. def process_ori_data(self):
  340. """
  341. 对原始数据进行处理,聚类和绘图
  342. :return:
  343. """
  344. self.turbine_clusters(self.ori_turbine_fft)
  345. self.paint_turbine()
  346. def turbine_clusters(self, data=None):
  347. """
  348. 风机数据聚类,聚类信息保存在self.cluster中
  349. :param data: 默认为空,也可以使用其他数据聚类,并体现在绘图中,
  350. 数据格式:二维数据n*m,n为数据条数,m为每条数据维数
  351. :return: None
  352. """
  353. if data is None:
  354. cluster = hierarchical_clustering(self.turbine_diff, threshold=1.4,
  355. similarity_func=compute_pearsonr) # 层次聚类
  356. else:
  357. cluster = hierarchical_clustering(data, threshold=1,
  358. similarity_func=compute_pearsonr)
  359. self.cluster = cluster
  360. from cluster_power import cluster_power_list_file, cluster_power_list_folder
  361. output_path = '../data-process/data/cluester_power/'
  362. cluster_power_list_file(self.cluster, self.turbine_id, input_path='../data-process/data/output_filtered_csv_files/', output_path=output_path)
  363. cluster_power_list_folder(self.cluster, self.turbine_id, input_path='../data-process/data/continuous_data/', output_path=output_path)
  364. data_analysis = DataAnalysis(data_length=9773,
  365. data_start=0,
  366. data_end=9773)
  367. data_analysis.process_ori_data()
  368. data_analysis.paint_double(20, 21)