Advertisement
VssA

231

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