Advertisement
VssA

123

Dec 14th, 2022
82
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 20.16 KB | None | 0 0
  1. import csv
  2. from openpyxl import Workbook
  3. from openpyxl.utils import get_column_letter
  4. from openpyxl.styles import Font, Border, Side
  5. import matplotlib.pyplot as plt
  6. import numpy as np
  7. from jinja2 import Environment, FileSystemLoader
  8. import pathlib
  9. import pdfkit
  10.  
  11. class DataSet:
  12. """
  13. Класс для представления датасета
  14. Attributes:
  15. file_name(Any): Названия файла датасета
  16. vacancy_name(Any): Название вакансии
  17. """
  18. def __init__(self, file_name, vacancy_name):
  19. """
  20. Инициализирует объект DataSet, считает общую зарплату, считает среднее значение зарплаты,
  21. считывает значение из файла, считает статистику, выводит значение получившиеся значения на консоль
  22. Args:
  23. file_name(Any): Названия файла датасета
  24. vacancy_name(Any): Название вакансии
  25. """
  26. self.file_name = file_name
  27. self.vacancy_name = vacancy_name
  28.  
  29. @staticmethod
  30. def add_amount_salary(salary_dict, key, amount_of_salary):
  31. """
  32. Вычисляет общуюю сумму зарплат
  33. :param
  34. salary_dict: словарь зарплат
  35. :param
  36. key: ключ для зарплаты по вакансиям
  37. :param
  38. amount_of_salary: нужное количество добавки зарплаты
  39. :return:
  40. void
  41. """
  42. if key in salary_dict:
  43. salary_dict[key] += amount_of_salary
  44. else:
  45. salary_dict[key] = amount_of_salary
  46.  
  47. @staticmethod
  48. def get_average_salary(salary_dict):
  49. """
  50. Считает словарь средних зарплат
  51. :param
  52. salary_dict: словарь зарплат
  53. :return:
  54. dict: словарь средних зарплат
  55. """
  56. new_dictionary = {}
  57. for key, values in salary_dict.items():
  58. new_dictionary[key] = int(sum(values) / len(values))
  59. return new_dictionary
  60.  
  61. def work_with_the_file(self):
  62. """
  63. Считывает значения с файла
  64. :return:
  65. void
  66. """
  67.  
  68. with open(self.file_name, mode='r', encoding='utf-8-sig') as file:
  69. reader_csv = csv.reader(file)
  70. header_of_the_file = next(reader_csv)
  71. header_length = len(header_of_the_file)
  72. for row_in_file in reader_csv:
  73. if '' not in row_in_file and len(row_in_file) == header_length:
  74. yield dict(zip(header_of_the_file, row_in_file))
  75.  
  76. def calculate_statistics(self):
  77. """
  78. Вычисляет статистику:
  79. Динамика уровня зарплат по годам,
  80. Динамика количества вакансий по годам,
  81. Динамика уровня зарплат по годам для выбранной профессии,
  82. Динамика количества вакансий по годам для выбранной професси,
  83. Уровень зарплат по городам,
  84. Доля вакансий по городам
  85. :return:
  86. void
  87. """
  88. salary = {}
  89. salary_of_vacancy_name = {}
  90. salary_city = {}
  91. count_of_vacancies = 0
  92. for vacancies_dict in self.work_with_the_file():
  93. vacancy = Vacancy(vacancies_dict)
  94. self.add_amount_salary(salary, vacancy.year, [vacancy.salary_average])
  95. if vacancy.name.find(self.vacancy_name) != -1:
  96. self.add_amount_salary(salary_of_vacancy_name, vacancy.year, [vacancy.salary_average])
  97. self.add_amount_salary(salary_city, vacancy.area_name, [vacancy.salary_average])
  98. count_of_vacancies += 1
  99. vac_num_dict = dict([(key, len(value)) for key, value in salary.items()])
  100. vac_by_name = dict([(key, len(value)) for key, value in salary_of_vacancy_name.items()])
  101. if not salary_of_vacancy_name:
  102. salary_of_vacancy_name = dict([(key, [0]) for key, value in salary.items()])
  103. vac_by_name = dict([(key, 0) for key, value in vac_num_dict.items()])
  104. average_salary = self.get_average_salary(salary)
  105. average_salary_vac = self.get_average_salary(salary_of_vacancy_name)
  106. average_salary_city = self.get_average_salary(salary_city)
  107. quantity_dynamics = {}
  108. for year, salaries in salary_city.items():
  109. quantity_dynamics[year] = round(len(salaries) / count_of_vacancies, 4)
  110. quantity_dynamics = list(filter(lambda a: a[-1] >= 0.01,
  111. [(key, value) for key, value in quantity_dynamics.items()]))
  112. quantity_dynamics.sort(key=lambda a: a[-1], reverse=True)
  113. top_ten_quantity = quantity_dynamics.copy()
  114. quantity_dynamics = dict(quantity_dynamics)
  115. average_salary_city = list(filter(lambda a: a[0] in list(quantity_dynamics.keys()),
  116. [(key, value) for key, value in average_salary_city.items()]))
  117. average_salary_city.sort(key=lambda a: a[-1], reverse=True)
  118. average_salary_city = dict(average_salary_city[:10])
  119. top_ten_quantity = dict(top_ten_quantity[:10])
  120.  
  121. return average_salary, vac_num_dict, average_salary_vac, vac_by_name, average_salary_city, top_ten_quantity
  122.  
  123. @staticmethod
  124. def print_statistic(stats1, stats2, stats3, stats4, stats5, stats6):
  125. """
  126. Выводит значение статистики на консоль
  127. :param
  128. stats1: Динамика уровня зарплат по годам
  129. :param
  130. stats2: Динамика количества вакансий по годам
  131. :param
  132. stats3: Динамика уровня зарплат по годам для выбранной профессии
  133. :param
  134. stats4: Динамика количества вакансий по годам для выбранной профессии
  135. :param
  136. stats5: Уровень зарплат по городам (в порядке убывания)
  137. :param
  138. stats6: Доля вакансий по городам (в порядке убывания)
  139. :return:
  140. void
  141. """
  142. print('Динамика уровня зарплат по годам: {0}'.format(stats1))
  143. print('Динамика количества вакансий по годам: {0}'.format(stats2))
  144. print('Динамика уровня зарплат по годам для выбранной профессии: {0}'.format(stats3))
  145. print('Динамика количества вакансий по годам для выбранной профессии: {0}'.format(stats4))
  146. print('Уровень зарплат по городам (в порядке убывания): {0}'.format(stats5))
  147. print('Доля вакансий по городам (в порядке убывания): {0}'.format(stats6))
  148.  
  149.  
  150. class InputConnect:
  151. """
  152. Класс, который принимает входные данные.
  153. Atributes:
  154. file_name(str): название файла
  155. vacancy_name(str): интирисующая профессия
  156. """
  157. def __init__(self):
  158. """
  159. Инициализирует объект InputConnect, вызывает методы для подсчета статистики,
  160. вызывает методы для вывода статистики на консоль,создает объект класса report
  161. """
  162. self.file_name = input('Введите название файла: ')
  163. self.vacancy_name = input('Введите название профессии: ')
  164. flag = input("Введите метод представления данных: ")
  165. dataset = DataSet(self.file_name, self.vacancy_name)
  166. salary_year = dataset.calculate_statistics()[0]
  167. vacancies_year = dataset.calculate_statistics()[1]
  168. vacancies_salary_name = dataset.calculate_statistics()[2]
  169. vacancies_quantity = dataset.calculate_statistics()[3]
  170. salary_city_decrease = dataset.calculate_statistics()[4]
  171. salary_city_increase = dataset.calculate_statistics()[5]
  172.  
  173. dataset.print_statistic(salary_year,
  174. vacancies_year,
  175. vacancies_salary_name,
  176. vacancies_quantity,
  177. salary_city_decrease,
  178. salary_city_increase)
  179.  
  180. report = Report(self.vacancy_name,
  181. salary_year,
  182. vacancies_year,
  183. vacancies_salary_name,
  184. vacancies_quantity,
  185. salary_city_decrease,
  186. salary_city_increase)
  187. if flag == "Вакансии":
  188. report.generate_excel()
  189. report.save('report.xlsx')
  190. elif flag == "Статистика":
  191. report.generate_image()
  192.  
  193.  
  194.  
  195. class Report:
  196. """
  197. Класс report создает таблицу excel с данными по интересующей профессии и графики со статистикой
  198. Attributes:
  199. wb(Workbook): лист excel
  200. vacancy_name(Any): название профессии
  201. stats1(Any): Динамика уровня зарплат по годам
  202. stats2(Any): Динамика количества вакансий по годам
  203. stats3(Any): Динамика уровня зарплат по годам для выбранной профессии
  204. stats4(Any): Динамика количества вакансий по годам для выбранной профессии
  205. stats5(Any): Уровень зарплат по городам (в порядке убывания)
  206. stats6(Any): Доля вакансий по городам (в порядке убывания)
  207. """
  208. def __init__(self, vacancy_name, stats1, stats2, stats3, stats4, stats5, stats6):
  209. """
  210. Инициализирует объект Report
  211. :param
  212. vacancy_name(Any): интересующая профессия
  213. :param
  214. stats1(Any): Динамика уровня зарплат по годам
  215. :param
  216. stats2(Any): Динамика количества вакансий по годам
  217. :param
  218. stats3(Any): Динамика уровня зарплат по годам для выбранной профессии
  219. :param
  220. stats4(Any): Динамика количества вакансий по годам для выбранной профессии
  221. :param
  222. stats5(Any): Уровень зарплат по городам (в порядке убывания)
  223. :param
  224. stats6(Any): Доля вакансий по городам (в порядке убывания)
  225. """
  226. self.wb = Workbook()
  227. self.vacancy_name = vacancy_name
  228. self.stats1 = stats1
  229. self.stats2 = stats2
  230. self.stats3 = stats3
  231. self.stats4 = stats4
  232. self.stats5 = stats5
  233. self.stats6 = stats6
  234.  
  235. def generate_excel(self):
  236. """
  237. генерирует excel файл с данными по профессии
  238. :return:
  239. void
  240. """
  241. ws1 = self.wb.active
  242. ws1.title = 'Статистика по годам'
  243. ws1.append(['Год',
  244. 'Средняя зарплата',
  245. 'Средняя зарплата - ' + self.vacancy_name,
  246. 'Количество вакансий',
  247. 'Количество вакансий - ' + self.vacancy_name])
  248.  
  249. for year in self.stats1.keys():
  250. ws1.append([year, self.stats1[year], self.stats3[year], self.stats2[year], self.stats4[year]])
  251.  
  252. data = [['Год ', 'Средняя зарплата ',
  253. ' Средняя зарплата - ' + self.vacancy_name,
  254. ' Количество вакансий',
  255. ' Количество вакансий - ' + self.vacancy_name]]
  256.  
  257. column_widths = []
  258. for row in data:
  259. for i, cell in enumerate(row):
  260. if len(column_widths) > i:
  261. if len(cell) > column_widths[i]:
  262. column_widths[i] = len(cell)
  263. else:
  264. column_widths += [len(cell)]
  265.  
  266. for i, column_width in enumerate(column_widths, 1): # ,1 to start at 1
  267. ws1.column_dimensions[get_column_letter(i)].width = column_width + 2
  268.  
  269. data = []
  270. data.append(['Город', 'Уровень зарплат', '', 'Город', 'Доля вакансий'])
  271. for (city1, value1), (city2, value2) in zip(self.stats5.items(), self.stats6.items()):
  272. data.append([city1, value1, '', city2, value2])
  273. ws2 = self.wb.create_sheet('Статистика по городам')
  274. for row in data:
  275. ws2.append(row)
  276.  
  277. column_widths = []
  278. for row in data:
  279. for i, cell in enumerate(row):
  280. cell = str(cell)
  281. if len(column_widths) > i:
  282. if len(cell) > column_widths[i]:
  283. column_widths[i] = len(cell)
  284. else:
  285. column_widths += [len(cell)]
  286.  
  287. for i, column_width in enumerate(column_widths, 1): # ,1 to start at 1
  288. ws2.column_dimensions[get_column_letter(i)].width = column_width + 2
  289.  
  290. font_bold = Font(bold=True)
  291. for col in 'ABCDE':
  292. ws1[col + '1'].font = font_bold
  293. ws2[col + '1'].font = font_bold
  294.  
  295. for index, _ in enumerate(self.stats5):
  296. ws2['E' + str(index + 2)].number_format = '0.00%'
  297.  
  298. thin = Side(border_style='thin', color='00000000')
  299.  
  300. for row in range(len(data)):
  301. for col in 'ABDE':
  302. ws2[col + str(row + 1)].border = Border(left=thin, bottom=thin, right=thin, top=thin)
  303.  
  304. for row, _ in enumerate(self.stats1):
  305. for col in 'ABCDE':
  306. ws1[col + str(row + 1)].border = Border(left=thin, bottom=thin, right=thin, top=thin)
  307.  
  308. def generate_image(self):
  309. """
  310. вызвает методы по созданию графиков, сохраняет полученное изображение в формате png
  311. :return:
  312. void
  313. """
  314. fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2)
  315. self.first_diagram(ax1)
  316. self.second_diagram(ax2)
  317. self.horizontal_diagram(ax3)
  318. self.round_diagram(ax4)
  319.  
  320. plt.tight_layout()
  321. plt.savefig('graph.png')
  322.  
  323. def round_diagram(self, ax4):
  324. """
  325. создает диаграмму - пирог доли вакансий по городам
  326. :param ax4(axes): диаграмма - пирог
  327. :return:
  328. void
  329. """
  330. ax4.set_title('Доля вакансий по городам', fontdict={'fontsize': 8})
  331. other = 1 - sum([value for value in self.stats6.values()])
  332. ax4.pie(list(self.stats6.values()) + [other], labels=list(self.stats6.keys()) + ['Другие'],
  333. textprops={'fontsize': 6})
  334.  
  335. def horizontal_diagram(self, ax3):
  336. """
  337. создает горизонтальную диаграмму уровень зарплат по городам
  338. :param ax3(axes): горизонтальная диаграмма
  339. :return:
  340. void
  341. """
  342. ax3.set_title('Уровень зарплат по городам', fontdict={'fontsize': 8})
  343. ax3.barh(list([str(a).replace(' ', '\n').replace('-', '-\n') for a in reversed(list(self.stats5.keys()))]),
  344. list(reversed(list(self.stats5.values()))), color='blue', height=0.5, align='center')
  345. ax3.yaxis.set_tick_params(labelsize=6)
  346. ax3.xaxis.set_tick_params(labelsize=8)
  347. ax3.grid(axis='x')
  348.  
  349. def second_diagram(self, ax2):
  350. """
  351. создает диаграмму, показывающую количество вакансий по годам
  352. :param ax2(axes): диаграмма
  353. :return:
  354. void
  355. """
  356. ax2.set_title('Количество вакансий по годам', fontdict={'fontsize': 8})
  357. bar1 = ax2.bar(np.array(list(self.stats2.keys())) - 0.4, self.stats2.values(), width=0.4)
  358. bar2 = ax2.bar(np.array(list(self.stats2.keys())), self.stats4.values(), width=0.4)
  359. ax2.legend((bar1[0], bar2[0]), ('Количество вакансий', 'Количество вакансий\n' + self.vacancy_name.lower()),
  360. prop={'size': 8})
  361. ax2.set_xticks(np.array(list(self.stats2.keys())) - 0.2, list(self.stats2.keys()), rotation=90)
  362. ax2.grid(axis='y')
  363. ax2.xaxis.set_tick_params(labelsize=8)
  364. ax2.yaxis.set_tick_params(labelsize=8)
  365.  
  366. def first_diagram(self, ax1):
  367. """
  368. создает горизонтальную диаграмму, показывающую уровень зарплат по годам
  369. :param ax4(axes):диаграмма
  370. :return:
  371. void
  372. """
  373. bar1 = ax1.bar(np.array(list(self.stats1.keys())) - 0.4, self.stats1.values(), width=0.4)
  374. bar2 = ax1.bar(np.array(list(self.stats1.keys())), self.stats3.values(), width=0.4)
  375. ax1.set_title('Уровень зарплат по годам', fontdict={'fontsize': 8})
  376. ax1.grid(axis='y')
  377. ax1.legend((bar1[0], bar2[0]), ('средняя з/п', 'з/п ' + self.vacancy_name.lower()), prop={'size': 8})
  378. ax1.set_xticks(np.array(list(self.stats1.keys())) - 0.2, list(self.stats1.keys()), rotation=90)
  379. ax1.xaxis.set_tick_params(labelsize=8)
  380. ax1.yaxis.set_tick_params(labelsize=8)
  381.  
  382. def save(self, filename):
  383. self.wb.save(filename=filename)
  384.  
  385.  
  386. class Vacancy:
  387. """
  388. Класс для представления зарплаты
  389. Attributes:
  390. vacancy(dict): интересующая вакансия
  391. """
  392. currency_in_rub = {
  393. "AZN": 35.68,
  394. "BYR": 23.91,
  395. "EUR": 59.90,
  396. "GEL": 21.74,
  397. "KGS": 0.76,
  398. "KZT": 0.13,
  399. "RUR": 1,
  400. "UAH": 1.64,
  401. "USD": 60.66,
  402. "UZS": 0.0055,
  403. }
  404.  
  405. def __init__(self, vacancy):
  406. """
  407. Инициализирует объект Salary, выполняет конвертацию валюты в рубли, считает среднее значение зарплаты в рублях
  408. :param vacancy: интересующая вакансия
  409. """
  410. self.name = vacancy['name']
  411. self.salary_from = int(float(vacancy['salary_from']))
  412. self.salary_to = int(float(vacancy['salary_to']))
  413. self.salary_currency = vacancy['salary_currency']
  414. salary_rub = self.currency_in_rub[self.salary_currency]
  415. salary_average_value = (self.salary_from + self.salary_to) / 2
  416. self.salary_average = salary_rub * salary_average_value
  417. self.area_name = vacancy['area_name']
  418. self.year = int(vacancy['published_at'][:4])
  419.  
  420.  
  421. if __name__ == '__main__':
  422. InputConnect()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement