3.3链接表 结构 P79页
用一个类图来表示EmployeeNode类的结构,展示其属性和关系:
plaintext
+-----------------------------------------+
| EmployeeNode |
+-----------------------------------------+
| - emp_no: int |
| - birth_date: date |
| - first_name: str |
| - last_name: str |
| - gender: str |
| - hire_date: date |
| - departments: list |
| - manager_of: list |
| - salaries: list |
| - titles: list |
+-----------------------------------------+
| + __init__(emp_no, birth_date, ...) |
| + __repr__(): str |
+-----------------------------------------+| | |v v v
+----------------+----------------+----------------+
| departments | manager_of | salaries |
| (部门关系) | (管理关系) | (薪资历史) |
+----------------+----------------+----------------+|v
+----------------+
| titles |
| (职位历史) |
+----------------+这个图表展示了:
EmployeeNode类的核心属性:
基本信息:员工编号 (emp_no)、出生日期 (birth_date)、姓名 (first_name/last_name)、性别 (gender)、入职日期 (hire_date)
关系属性:部门列表 (departments)、管理部门列表 (manager_of)、薪资历史 (salaries)、职位历史 (titles)
类的方法:
构造方法__init__用于初始化对象
__repr__方法用于返回对象的字符串表示
关系说明:
departments:存储员工所属的部门及任职时间信息
manager_of:存储员工管理的部门及管理时间信息
salaries:存储员工的薪资历史记录
titles:存储员工的职位变迁历史
这些属性共同构成了一个员工的完整档案,包括基本信息和各种职业相关的关系信息。
程序类关系总览图
plaintext
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
│ EmployeeNode │ │ DepartmentNode │ │ SalaryNode │
├───────────────────┤ ├───────────────────┤ ├───────────────────┤
│ - emp_no │ │ - dept_no │ │ - emp_no │
│ - birth_date │ │ - dept_name │ │ - salary │
│ - first_name │ │ - employees │◄──────┤ - from_date │
│ - last_name │ │ - managers │ │ - to_date │
│ - gender │ └───────────────────┘ └───────────────────┘
│ - hire_date │ ▲ ▲
│ - departments │◄───────────────┘ │
│ - manager_of │ │
│ - salaries │◄────────────────────────────────────────┘
│ - titles │◄────────────────────────────────────────┐
└───────────────────┘ ││
┌───────────────────┐ ┌───────────────────┐ │
│ TitleNode │ │ EmployeeGraph │ │
├───────────────────┤ ├───────────────────┤ │
│ - emp_no │ │ - connection │ │
│ - title │ │ - employees │◄────────────┘
│ - from_date │ │ - departments │
│ - to_date │ │ - employees_by_name│
└───────────────────┘ └───────────────────┘▲ ▲│ │└─────────────────────────┘(被管理/被构建)图表说明
1. 核心节点类(数据实体)
EmployeeNode(员工节点)
存储员工基本信息(编号、姓名、出生日期等)。
通过列表属性关联其他实体:
departments:关联所属的DepartmentNode及任职时间。
manager_of:关联管理的DepartmentNode及管理时间。
salaries:关联对应的SalaryNode(薪资历史)。
titles:关联对应的TitleNode(职位历史)。
DepartmentNode(部门节点)
存储部门基本信息(编号、名称)。
通过列表属性关联员工:
employees:关联属于该部门的EmployeeNode及任职时间。
managers:关联管理该部门的EmployeeNode及管理时间。
SalaryNode(薪资节点)
存储单条薪资记录(金额、生效 / 截止日期),通过emp_no关联EmployeeNode。
TitleNode(职位节点)
存储单条职位记录(职位名称、生效 / 截止日期),通过emp_no关联EmployeeNode。
2. 管理类(核心逻辑)
EmployeeGraph(员工关系图)
负责数据库连接、数据加载和关系构建。
存储所有节点的集合:
employees:以emp_no为键的EmployeeNode字典。
departments:以dept_no为键的DepartmentNode字典。
employees_by_name:以姓名为键的EmployeeNode字典(方便按姓名查询)。
核心方法:通过数据库查询,将分散的节点数据关联起来(如员工 - 部门、员工 - 薪资等关系)。
3. 关系总结
一对一关联:SalaryNode和TitleNode通过emp_no绑定到特定EmployeeNode。
一对多关联:
一个DepartmentNode对应多个EmployeeNode(员工)和多个EmployeeNode(经理)。
一个EmployeeNode对应多个DepartmentNode(多部门任职)、多个SalaryNode(薪资变动)、多个TitleNode(职位变动)。
管理关系:EmployeeGraph是整个程序的 “引擎”,负责初始化所有节点并建立它们之间的关联。
通过这个图表,可以直观理解程序的设计思路:用面向对象的方式将数据库中的表结构映射为节点类,再通过EmployeeGraph类构建节点间的关联,最终形成一个可查询的员工关系网络。
这段代码实现了一个员工信息管理系统,通过 MySQL 数据库构建了一个员工关系图,能够加载和展示员工、部门、薪资和职位等信息及其关系。下面我来逐句讲解:
python
运行
import mysql.connector
from mysql.connector import Error
from datetime import date
导入所需的库:mysql.connector用于连接 MySQL 数据库,Error用于捕获数据库错误,date用于处理日期数据
python
运行
# 首先定义所有节点类
class EmployeeNode:def __init__(self, emp_no, birth_date, first_name, last_name, gender, hire_date):self.emp_no = emp_noself.birth_date = birth_dateself.first_name = first_nameself.last_name = last_nameself.gender = genderself.hire_date = hire_dateself.departments = [] # 员工所属的部门列表self.manager_of = [] # 员工管理的部门列表self.salaries = [] # 员工的薪资历史self.titles = [] # 员工的职位历史
定义EmployeeNode类,用于表示员工节点
__init__方法初始化员工的基本信息
同时初始化了几个列表属性,用于存储与该员工相关的部门、管理关系、薪资和职位信息
python
运行def __repr__(self):return f"<Employee {self.emp_no}: {self.first_name} {self.last_name}>"
定义__repr__方法,用于返回员工对象的字符串表示,方便调试和打印
python
运行
class DepartmentNode:def __init__(self, dept_no, dept_name):self.dept_no = dept_noself.dept_name = dept_nameself.employees = [] # 部门中的员工列表self.managers = [] # 部门的经理列表
定义DepartmentNode类,用于表示部门节点
__init__方法初始化部门的基本信息
初始化了两个列表属性,用于存储部门中的员工和经理信息
python
运行def __repr__(self):return f"<Department {self.dept_no}: {self.dept_name}>"
定义__repr__方法,用于返回部门对象的字符串表示
python
运行
class SalaryNode:def __init__(self, emp_no, salary, from_date, to_date):self.emp_no = emp_noself.salary = salaryself.from_date = from_dateself.to_date = to_date
定义SalaryNode类,用于表示薪资节点
__init__方法初始化薪资信息,包括员工编号、薪资数额、生效日期和截止日期
python
运行def __repr__(self):return f"<Salary {self.salary} from {self.from_date} to {self.to_date}>"
定义__repr__方法,用于返回薪资对象的字符串表示
python
运行
class TitleNode:def __init__(self, emp_no, title, from_date, to_date):self.emp_no = emp_noself.title = titleself.from_date = from_dateself.to_date = to_date
定义TitleNode类,用于表示职位节点
__init__方法初始化职位信息,包括员工编号、职位名称、生效日期和截止日期
python
运行def __repr__(self):return f"<Title {self.title} from {self.from_date} to {self.to_date}>"
定义__repr__方法,用于返回职位对象的字符串表示
python
运行
# 然后定义EmployeeGraph类
class EmployeeGraph:def __init__(self, host, database, user, password):try:self.connection = mysql.connector.connect(host=host,database=database,user=user,password=password)self.employees = {} # 存储员工对象,键为员工编号self.departments = {} # 存储部门对象,键为部门编号self.employees_by_name = {} # 存储员工对象,键为员工姓名(小写)except Error as e:print(f"Error connecting to MySQL: {e}")
定义EmployeeGraph类,用于构建和管理员工关系图
__init__方法接收数据库连接参数,尝试连接 MySQL 数据库
初始化了三个字典属性,用于存储员工、部门对象,以及按姓名索引的员工对象
使用try-except捕获数据库连接错误
python
运行def load_employees(self):query = "SELECT * FROM employees"cursor = self.connection.cursor(dictionary=True)cursor.execute(query)for row in cursor:emp = EmployeeNode(emp_no=row['emp_no'],birth_date=row['birth_date'],first_name=row['first_name'],last_name=row['last_name'],gender=row['gender'],hire_date=row['hire_date'])self.employees[row['emp_no']] = empname_key = f"{row['first_name']} {row['last_name']}".lower()self.employees_by_name[name_key] = empcursor.close()
定义load_employees方法,用于从数据库加载员工信息
执行 SQL 查询获取所有员工记录
为每条记录创建EmployeeNode对象,并存储到employees和employees_by_name字典中
最后关闭游标
python
运行def load_departments(self):query = "SELECT * FROM departments"cursor = self.connection.cursor(dictionary=True)cursor.execute(query)for row in cursor:dept = DepartmentNode(dept_no=row['dept_no'],dept_name=row['dept_name'])self.departments[row['dept_no']] = deptcursor.close()
定义load_departments方法,用于从数据库加载部门信息
执行 SQL 查询获取所有部门记录
为每条记录创建DepartmentNode对象,并存储到departments字典中
最后关闭游标
python
运行def build_dept_emp_relationships(self):query = """SELECT de.emp_no, de.dept_no, de.from_date, de.to_date, d.dept_name FROM dept_emp deJOIN departments d ON de.dept_no = d.dept_no"""cursor = self.connection.cursor(dictionary=True)cursor.execute(query)for row in cursor:emp = self.employees.get(row['emp_no'])dept = self.departments.get(row['dept_no'])if emp and dept:dept.employees.append({'employee': emp,'from_date': row['from_date'],'to_date': row['to_date']})emp.departments.append({'department': dept,'from_date': row['from_date'],'to_date': row['to_date']})cursor.close()
定义build_dept_emp_relationships方法,用于构建部门和员工之间的关系
执行 SQL 查询获取员工与部门的关联信息
对每条记录,获取对应的员工和部门对象
如果两者都存在,则在部门对象的employees列表和员工对象的departments列表中添加关联信息
最后关闭游标
python
运行def build_dept_manager_relationships(self):query = """SELECT dm.emp_no, dm.dept_no, dm.from_date, dm.to_date, d.dept_name FROM dept_manager dmJOIN departments d ON dm.dept_no = d.dept_no"""cursor = self.connection.cursor(dictionary=True)cursor.execute(query)for row in cursor:emp = self.employees.get(row['emp_no'])dept = self.departments.get(row['dept_no'])if emp and dept:dept.managers.append({'manager': emp,'from_date': row['from_date'],'to_date': row['to_date']})emp.manager_of.append({'department': dept,'from_date': row['from_date'],'to_date': row['to_date']})cursor.close()
定义build_dept_manager_relationships方法,用于构建部门和经理之间的关系
执行 SQL 查询获取经理与部门的关联信息
对每条记录,获取对应的员工 (经理) 和部门对象
如果两者都存在,则在部门对象的managers列表和员工对象的manager_of列表中添加关联信息
最后关闭游标
python
运行def load_salaries(self):query = "SELECT * FROM salaries"cursor = self.connection.cursor(dictionary=True)cursor.execute(query)for row in cursor:emp = self.employees.get(row['emp_no'])if emp:salary = SalaryNode(emp_no=row['emp_no'],salary=row['salary'],from_date=row['from_date'],to_date=row['to_date'])emp.salaries.append(salary)cursor.close()
定义load_salaries方法,用于加载员工的薪资信息
执行 SQL 查询获取所有薪资记录
对每条记录,获取对应的员工对象
如果员工存在,则创建SalaryNode对象,并添加到员工的salaries列表中
最后关闭游标
python
运行def load_titles(self):query = "SELECT * FROM titles"cursor = self.connection.cursor(dictionary=True)cursor.execute(query)for row in cursor:emp = self.employees.get(row['emp_no'])if emp:title = TitleNode(emp_no=row['emp_no'],title=row['title'],from_date=row['from_date'],to_date=row['to_date'])emp.titles.append(title)cursor.close()
定义load_titles方法,用于加载员工的职位信息
执行 SQL 查询获取所有职位记录
对每条记录,获取对应的员工对象
如果员工存在,则创建TitleNode对象,并添加到员工的titles列表中
最后关闭游标
python
运行def build_graph(self):self.load_employees()self.load_departments()self.build_dept_emp_relationships()self.build_dept_manager_relationships()self.load_salaries()self.load_titles()
定义build_graph方法,用于构建完整的员工关系图
依次调用前面定义的各个方法,加载和关联所有数据
python
运行def close(self):if hasattr(self, 'connection') and self.connection.is_connected():self.connection.close()
定义close方法,用于关闭数据库连接
检查连接是否存在且处于连接状态,如果是则关闭连接
python
运行
# 使用示例
if __name__ == "__main__":# 初始化并构建图graph = EmployeeGraph(host="localhost",database="employees",user="root",password="root")graph.build_graph()
当脚本作为主程序运行时的代码
创建EmployeeGraph对象,传入数据库连接参数
调用build_graph方法构建员工关系图
python
运行# 示例查询if 10001 in graph.employees:emp = graph.employees[10001]print(emp)print("所属部门:", emp.departments)print("管理的部门:", emp.manager_of)print("薪资历史:", emp.salaries)print("职位历史:", emp.titles)
示例查询代码,检查员工编号为 10001 的员工是否存在
如果存在,则打印该员工的基本信息、所属部门、管理的部门、薪资历史和职位历史
python
运行# 分析职业发展print(f"\n职业发展分析 - {emp.first_name} {emp.last_name}")print(f"入职时间: {emp.hire_date}")print(f"当前职位: {emp.titles[-1].title if emp.titles else '无记录'}")print(f"当前部门: {emp.departments[-1]['department'].dept_name if emp.departments else '无记录'}")if emp.salaries:growth = (emp.salaries[-1].salary / emp.salaries[0].salary) - 1print(f"薪资增长: {emp.salaries[0].salary} → {emp.salaries[-1].salary} (增长{growth:.1%})")
对员工进行职业发展分析
打印入职时间、当前职位 (假设最后一个职位是当前职位)、当前部门 (假设最后一个部门是当前部门)
计算并打印薪资增长情况
python
运行# 关闭连接graph.close()最后调用close方法关闭数据库连接
这个程序通过面向对象的方式构建了一个员工关系图,将数据库中的员工、部门、薪资和职位信息及其关系映射为内存中的对象模型,方便进行各种查询和分析操作。
import mysql.connector
from mysql.connector import Error
from datetime import date
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd# 首先定义所有节点类
class EmployeeNode:def __init__(self, emp_no, birth_date, first_name, last_name, gender, hire_date):self.emp_no = emp_noself.birth_date = birth_dateself.first_name = first_nameself.last_name = last_nameself.gender = genderself.hire_date = hire_dateself.departments = []self.manager_of = []self.salaries = []self.titles = []def __repr__(self):return f"<Employee {self.emp_no}: {self.first_name} {self.last_name}>"class DepartmentNode:def __init__(self, dept_no, dept_name):self.dept_no = dept_noself.dept_name = dept_nameself.employees = []self.managers = []def __repr__(self):return f"<Department {self.dept_no}: {self.dept_name}>"class SalaryNode:def __init__(self, emp_no, salary, from_date, to_date):self.emp_no = emp_noself.salary = salaryself.from_date = from_dateself.to_date = to_datedef __repr__(self):return f"<Salary {self.salary} from {self.from_date} to {self.to_date}>"class TitleNode:def __init__(self, emp_no, title, from_date, to_date):self.emp_no = emp_noself.title = titleself.from_date = from_dateself.to_date = to_datedef __repr__(self):return f"<Title {self.title} from {self.from_date} to {self.to_date}>"# 然后定义EmployeeGraph类
class EmployeeGraph:def __init__(self, host, database, user, password):try:self.connection = mysql.connector.connect(host=host,database=database,user=user,password=password)self.employees = {}self.departments = {}self.employees_by_name = {}except Error as e:print(f"Error connecting to MySQL: {e}")def load_employees(self):query = "SELECT * FROM employees"cursor = self.connection.cursor(dictionary=True)cursor.execute(query)for row in cursor:emp = EmployeeNode(emp_no=row['emp_no'],birth_date=row['birth_date'],first_name=row['first_name'],last_name=row['last_name'],gender=row['gender'],hire_date=row['hire_date'])self.employees[row['emp_no']] = empname_key = f"{row['first_name']} {row['last_name']}".lower()self.employees_by_name[name_key] = empcursor.close()def load_departments(self):query = "SELECT * FROM departments"cursor = self.connection.cursor(dictionary=True)cursor.execute(query)for row in cursor:dept = DepartmentNode(dept_no=row['dept_no'],dept_name=row['dept_name'])self.departments[row['dept_no']] = deptcursor.close()def build_dept_emp_relationships(self):query = """SELECT de.emp_no, de.dept_no, de.from_date, de.to_date, d.dept_name FROM dept_emp deJOIN departments d ON de.dept_no = d.dept_no"""cursor = self.connection.cursor(dictionary=True)cursor.execute(query)for row in cursor:emp = self.employees.get(row['emp_no'])dept = self.departments.get(row['dept_no'])if emp and dept:dept.employees.append({'employee': emp,'from_date': row['from_date'],'to_date': row['to_date']})emp.departments.append({'department': dept,'from_date': row['from_date'],'to_date': row['to_date']})cursor.close()def build_dept_manager_relationships(self):query = """SELECT dm.emp_no, dm.dept_no, dm.from_date, dm.to_date, d.dept_name FROM dept_manager dmJOIN departments d ON dm.dept_no = d.dept_no"""cursor = self.connection.cursor(dictionary=True)cursor.execute(query)for row in cursor:emp = self.employees.get(row['emp_no'])dept = self.departments.get(row['dept_no'])if emp and dept:dept.managers.append({'manager': emp,'from_date': row['from_date'],'to_date': row['to_date']})emp.manager_of.append({'department': dept,'from_date': row['from_date'],'to_date': row['to_date']})cursor.close()def load_salaries(self):query = "SELECT * FROM salaries"cursor = self.connection.cursor(dictionary=True)cursor.execute(query)for row in cursor:emp = self.employees.get(row['emp_no'])if emp:salary = SalaryNode(emp_no=row['emp_no'],salary=row['salary'],from_date=row['from_date'],to_date=row['to_date'])emp.salaries.append(salary)cursor.close()def load_titles(self):query = "SELECT * FROM titles"cursor = self.connection.cursor(dictionary=True)cursor.execute(query)for row in cursor:emp = self.employees.get(row['emp_no'])if emp:title = TitleNode(emp_no=row['emp_no'],title=row['title'],from_date=row['from_date'],to_date=row['to_date'])emp.titles.append(title)cursor.close()def build_graph(self):self.load_employees()self.load_departments()self.build_dept_emp_relationships()self.build_dept_manager_relationships()self.load_salaries()self.load_titles()def close(self):if hasattr(self, 'connection') and self.connection.is_connected():self.connection.close()# 使用示例
if __name__ == "__main__":# 初始化并构建图graph = EmployeeGraph(host="localhost",database="employees",user="root",password="root")graph.build_graph()# 示例查询if 10001 in graph.employees:emp = graph.employees[10001]print(emp)print("所属部门:", emp.departments)print("管理的部门:", emp.manager_of)print("薪资历史:", emp.salaries)print("职位历史:", emp.titles)# 分析职业发展print(f"\n职业发展分析 - {emp.first_name} {emp.last_name}")print(f"入职时间: {emp.hire_date}")print(f"当前职位: {emp.titles[-1].title if emp.titles else '无记录'}")print(f"当前部门: {emp.departments[-1]['department'].dept_name if emp.departments else '无记录'}")if emp.salaries:growth = (emp.salaries[-1].salary / emp.salaries[0].salary) - 1print(f"薪资增长: {emp.salaries[0].salary} → {emp.salaries[-1].salary} (增长{growth:.1%})")# 准备薪资数据
salaries = emp.salaries
salary_data = [(s.from_date.year, s.salary) for s in salaries]
df_salary = pd.DataFrame(salary_data, columns=['Year', 'Salary'])# 创建图表
plt.figure(figsize=(12, 6))
sns.lineplot(data=df_salary, x='Year', y='Salary', marker='o')# 添加标注
for i, row in df_salary.iterrows():plt.text(row['Year'], row['Salary'], f"{row['Salary']:,}", ha='center', va='bottom')plt.title(f"{emp.first_name} {emp.last_name} 薪资增长趋势 (1986-2002)")
plt.xlabel("年份")
plt.ylabel("薪资 ($)")
plt.grid(True)
plt.tight_layout()
plt.show()# 关闭连接graph.close()
===============================================
<Employee 10001: Georgi Facello>
所属部门: [{'department': <Department d005: Development>, 'from_date': datetime.date(1986, 6, 26), 'to_date': datetime.date(9999, 1, 1)}]
管理的部门: []
薪资历史: [<Salary 60117 from 1986-06-26 to 1987-06-26>, <Salary 62102 from 1987-06-26 to 1988-06-25>, <Salary 66074 from 1988-06-25 to 1989-06-25>, <Salary 66596 from 1989-06-25 to 1990-06-25>, <Salary 66961 from 1990-06-25 to 1991-06-25>, <Salary 71046 from 1991-06-25 to 1992-06-24>, <Salary 74333 from 1992-06-24 to 1993-06-24>, <Salary 75286 from 1993-06-24 to 1994-06-24>, <Salary 75994 from 1994-06-24 to 1995-06-24>, <Salary 76884 from 1995-06-24 to 1996-06-23>, <Salary 80013 from 1996-06-23 to 1997-06-23>, <Salary 81025 from 1997-06-23 to 1998-06-23>, <Salary 81097 from 1998-06-23 to 1999-06-23>, <Salary 84917 from 1999-06-23 to 2000-06-22>, <Salary 85112 from 2000-06-22 to 2001-06-22>, <Salary 85097 from 2001-06-22 to 2002-06-22>, <Salary 88958 from 2002-06-22 to 9999-01-01>]
职位历史: [<Title Senior Engineer from 1986-06-26 to 9999-01-01>]职业发展分析 - Georgi Facello
入职时间: 1986-06-26
当前职位: Senior Engineer
当前部门: Development
薪资增长: 60117 → 88958 (增长48.0%)================================================
这个输出结果展示了员工Georgi Facello(员工号10001)的完整职业发展数据,分析如下:
职业发展概况
1. 基本信息:
o 姓名:Georgi Facello
o 入职时间:1986年6月26日
o 当前状态:仍在职(9999-01-01是数据库表示当前的标准方式)
2. 部门信息:
o 所属部门:Development (d005)
o 从1986年6月26日至今一直在同一部门
o 没有担任过管理职务(管理的部门为空列表)
3. 职位发展:
o 职位:Senior Engineer
o 从入职至今一直保持同一职位
o 没有职位变更记录
4. 薪资变化:
o 起薪:60,117 (1986年)
o 当前薪资:88,958 (2002年调整后)
o 总增长幅度:48.0%
o 共经历17次薪资调整
o 平均每年增长约3.0%(按16年计算)
详细薪资调整分析
text
复制
下载
1986-1987: 60,117 → 62,102 (+3.3%)
1987-1988: 62,102 → 66,074 (+6.4%)
1988-1989: 66,074 → 66,596 (+0.8%)
1989-1990: 66,596 → 66,961 (+0.5%)
1990-1991: 66,961 → 71,046 (+6.1%)
1991-1992: 71,046 → 74,333 (+4.6%)
1992-1993: 74,333 → 75,286 (+1.3%)
1993-1994: 75,286 → 75,994 (+0.9%)
1994-1995: 75,994 → 76,884 (+1.2%)
1995-1996: 76,884 → 80,013 (+4.1%)
1996-1997: 80,013 → 81,025 (+1.3%)
1997-1998: 81,025 → 81,097 (+0.1%)
1998-1999: 81,097 → 84,917 (+4.7%)
1999-2000: 84,917 → 85,112 (+0.2%)
2000-2001: 85,112 → 85,097 (-0.02%)
2001-2002: 85,097 → 88,958 (+4.5%)
职业发展特点
1. 稳定性强:
o 36年+任职同一部门
o 长期保持Senior Engineer职位
2. 薪资增长模式:
o 早期增长较快(前5年增长约18.5%)
o 中期增长平稳(1991-2002年平均约3.5%)
o 2000-2001年出现唯一一次微小降薪(可能是数据错误或特殊调整)
3. 职业发展建议:
o 可考虑横向发展(跨部门项目)
o 如寻求晋升,可能需要拓展管理技能
o 当前薪资增长率已趋于稳定,可能需要新的职业突破点
Python的matplotlib和seaborn库来创建专业图表。
1. 薪资增长趋势图(折线图)
python
复制
下载
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd# 准备薪资数据
salaries = emp.salaries
salary_data = [(s.from_date.year, s.salary) for s in salaries]
df_salary = pd.DataFrame(salary_data, columns=['Year', 'Salary'])# 创建图表
plt.figure(figsize=(12, 6))
sns.lineplot(data=df_salary, x='Year', y='Salary', marker='o')# 添加标注
for i, row in df_salary.iterrows():plt.text(row['Year'], row['Salary'], f"{row['Salary']:,}", ha='center', va='bottom')plt.title(f"{emp.first_name} {emp.last_name} 薪资增长趋势 (1986-2002)")
plt.xlabel("年份")
plt.ylabel("薪资 ($)")
plt.grid(True)
plt.tight_layout()
plt.show()
- 薪资年增长率分析(柱状图)
python
复制
下载
# 计算年增长率
df_salary['Growth'] = df_salary['Salary'].pct_change() * 100# 创建图表
plt.figure(figsize=(12, 6))
bars = plt.bar(df_salary['Year'][1:], df_salary['Growth'][1:], color=['green' if x >0 else 'red' for x in df_salary['Growth'][1:]])# 添加数值标签
for bar in bars:height = bar.get_height()plt.text(bar.get_x() + bar.get_width()/2., height,f'{height:.1f}%',ha='center', va='bottom')plt.title("年度薪资增长率 (%)")
plt.xlabel("年份")
plt.ylabel("增长率 (%)")
plt.axhline(0, color='black', linewidth=0.5)
plt.grid(axis='y')
plt.show()
3. 薪资分布箱线图(与全公司对比)
python
复制
下载
# 获取全公司同年入职员工薪资数据(示例)
# 实际应用中需要从数据库查询真实数据
company_salaries = {1986: [60117, 65000, 58000, 62000, 59000],2002: [85000, 92000, 88000, 89000, 91000]
}# 创建对比数据
compare_data = {'Year': [1986, 2002, 1986, 2002],'Salary': [emp.salaries[0].salary, emp.salaries[-1].salary, sum(company_salaries[1986])/len(company_salaries[1986]),sum(company_salaries[2002])/len(company_salaries[2002])],'Type': ['Georgi', 'Georgi', '公司平均', '公司平均']
}df_compare = pd.DataFrame(compare_data)# 创建图表
plt.figure(figsize=(10, 6))
sns.barplot(data=df_compare, x='Year', y='Salary', hue='Type', palette='Set2')plt.title("薪资与公司平均水平对比")
plt.xlabel("年份")
plt.ylabel("薪资 ($)")
plt.legend(title='')
plt.grid(axis='y')
plt.show()
- 职业发展路径图(甘特图)
python
复制
下载
from matplotlib.patches import Patch# 准备数据
events = [{"Type": "职位", "Start": emp.titles[0].from_date, "End": emp.titles[0].to_date, "Label": emp.titles[0].title},{"Type": "部门", "Start": emp.departments[0]['from_date'], "End": emp.departments[0]['to_date'], "Label": emp.departments[0]['department'].dept_name}
]# 转换为DataFrame
df_events = pd.DataFrame(events)# 创建图表
fig, ax = plt.subplots(figsize=(12, 4))colors = {'职位': 'tab:blue', '部门': 'tab:orange'}
for idx, row in df_events.iterrows():ax.barh(row['Type'], (row['End'] - row['Start']).days/365, left=row['Start'].year + (row['Start'].timetuple().tm_yday-1)/365,color=colors[row['Type']])ax.text((row['Start'].year + row['End'].year)/2, idx, row['Label'], ha='center', va='center', color='white')ax.set_xlim(1985, 2023)
ax.set_title("职业发展时间线")
ax.set_xlabel("年份")
ax.set_yticks(range(len(df_events)))
ax.set_yticklabels(df_events['Type'])
ax.grid(axis='x')# 添加图例
legend_elements = [Patch(facecolor=colors[k], label=k) for k in colors]
ax.legend(handles=legend_elements, loc='upper right')plt.tight_layout()
plt.show()
- 薪资预测(线性回归)
python
复制
下载
from sklearn.linear_model import LinearRegression
import numpy as np# 准备数据
X = np.array(df_salary['Year']).reshape(-1, 1)
y = np.array(df_salary['Salary'])# 训练模型
model = LinearRegression()
model.fit(X, y)# 预测未来
future_years = np.array([2005, 2010, 2015, 2020]).reshape(-1, 1)
predicted = model.predict(future_years)# 可视化
plt.figure(figsize=(12, 6))
plt.scatter(X, y, color='blue', label='实际薪资')
plt.plot(np.concatenate([X, future_years]), model.predict(np.concatenate([X, future_years]).reshape(-1, 1)),color='red', linestyle='--', label='预测趋势')plt.title("薪资增长趋势与预测")
plt.xlabel("年份")
plt.ylabel("薪资 ($)")
plt.legend()
plt.grid(True)# 标注预测值
for year, salary in zip(future_years.flatten(), predicted):plt.text(year, salary, f"{int(salary):,}", ha='center', va='bottom')plt.show()
使用建议
1. 安装必要库:
bash
复制
下载
pip install matplotlib seaborn scikit-learn pandas
2. 数据分析扩展:
o 可以添加更多员工数据进行对比分析
o 引入更多机器学习模型(如随机森林)提高预测准确性
o 添加交互功能(使用Plotly库)
3. 商业洞察:
o 识别高绩效员工成长模式
o 分析薪资增长与职位晋升的关系
o 预测未来人力成本
设计一个员工筛选生成器,可以找出同一年入职的员工。
功能说明
核心功能:get_employees_by_year(): 获取特定年份入职的所有员工analyze_hire_years(): 统计每年入职人数generate_report(): 生成指定年份区间的入职员工详细报告visualize_hire_trends(): 可视化每年入职人数趋势高级功能:支持按年份范围筛选自动将结果转换为Pandas DataFrame便于分析提供可视化图表展示入职趋势使用场景:python
# 查找所有1995年入职的员工
filter = EmployeeFilter(...)
employees_1995 = filter.get_employees_by_year(1995)# 分析1990-2000年入职情况
report = filter.generate_report(1990, 2000)
print(report.groupby('入职年份')['员工编号'].count())# 找出公司历史上入职人数最多的年份
stats = filter.analyze_hire_years()
peak_year = max(stats.items(), key=lambda x: x[1])
print(f"入职高峰年: {peak_year[0]}年, {peak_year[1]}人")
import mysql.connector
from mysql.connector import Error
from collections import defaultdict
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
import warnings# 1. 解决中文显示问题(无需外部字体文件)
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei'] # 使用系统自带雅黑字体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题# 2. 过滤警告信息
warnings.filterwarnings('ignore', category=UserWarning)class EmployeeFilter:def __init__(self, host, database, user, password, batch_size=50000):"""初始化数据库连接"""try:self.connection = mysql.connector.connect(host=host,database=database,user=user,password=password)self.employees_by_year = defaultdict(list)self.batch_size = batch_size # 分批处理大小except Error as e:print(f"数据库连接错误: {e}")def load_employees_by_year_batch(self):"""分批按入职年份加载员工数据"""offset = 0while True:query = f"""SELECT emp_no, first_name, last_name, gender, hire_date, YEAR(hire_date) as hire_yearFROM employeesORDER BY hire_dateLIMIT {self.batch_size} OFFSET {offset}"""cursor = self.connection.cursor(dictionary=True)cursor.execute(query)rows = cursor.fetchall()cursor.close()if not rows:breakfor row in rows:hire_year = row['hire_year']self.employees_by_year[hire_year].append({'emp_no': row['emp_no'],'name': f"{row['first_name']} {row['last_name']}",'gender': row['gender'],'hire_date': row['hire_date']})offset += self.batch_sizedef get_employees_by_year(self, year=None, max_display=10):"""获取特定年份入职的员工(限制显示数量):param year: 年份:param max_display: 最大显示数量:return: 格式化字符串结果"""if not self.employees_by_year:self.load_employees_by_year_batch()if year:employees = self.employees_by_year.get(year, [])result = f"{year}年入职员工(共{len(employees)}人):\n"for emp in employees[:max_display]:result += f"{emp['emp_no']}: {emp['name']} ({emp['gender']}) - {emp['hire_date']}\n"if len(employees) > max_display:result += f"...(仅显示前{max_display}条,共{len(employees)}条)"return resultelse:return "请指定具体年份"def analyze_hire_years(self):"""分析每年入职人数统计"""if not self.employees_by_year:self.load_employees_by_year_batch()stats = {year: len(employees) for year, employees in self.employees_by_year.items()}return statsdef visualize_hire_trends(self, start_year=None, end_year=None):"""可视化每年入职人数趋势(优化大数据处理)"""stats = self.analyze_hire_years()# 筛选年份范围years = sorted(stats.keys())if start_year:years = [y for y in years if y >= start_year]if end_year:years = [y for y in years if y <= end_year]counts = [stats[y] for y in years]# 创建图表fig, ax = plt.subplots(figsize=(14, 6))# 使用条形图更适合离散年份数据bars = ax.bar(years, counts, color='#1f77b4', alpha=0.7)# 设置中文标题和标签ax.set_title("公司每年入职员工数量趋势", fontsize=14, pad=20)ax.set_xlabel("入职年份", fontsize=12)ax.set_ylabel("员工数量", fontsize=12)# 优化刻度显示ax.set_xticks(years)ax.set_xticklabels(years, rotation=45, ha='right')# 添加网格和数据标签ax.grid(axis='y', linestyle='--', alpha=0.6)for bar in bars:height = bar.get_height()ax.text(bar.get_x() + bar.get_width()/2., height,f'{height:,}', ha='center', va='bottom', fontsize=9)# 自动调整布局plt.tight_layout()return figdef generate_report(self, start_year=None, end_year=None, sample_size=5):"""生成精简版报告(避免大数据量输出)"""stats = self.analyze_hire_years()# 筛选年份范围years = sorted(stats.keys())if start_year:years = [y for y in years if y >= start_year]if end_year:years = [y for y in years if y <= end_year]result = "员工入职年份统计报告\n"result += "="*40 + "\n"for year in years:count = stats[year]employees = self.employees_by_year[year]result += f"\n{year}年: 共{count}人\n"result += "示例员工:\n"# 显示样本员工for emp in employees[:sample_size]:result += f" - {emp['emp_no']}: {emp['name']} ({emp['gender']})\n"if count > sample_size:result += f" ...(共{count}人)\n"return resultdef close(self):"""关闭数据库连接"""if hasattr(self, 'connection') and self.connection.is_connected():self.connection.close()# 使用示例
if __name__ == "__main__":# 初始化筛选器filter = EmployeeFilter(host="localhost",database="employees",user="root",password="root")try:# 示例1:获取特定年份入职员工(限制输出)print(filter.get_employees_by_year(1986))# 示例2:生成精简报告print("\n" + filter.generate_report(1985, 1990))# 示例3:可视化1985-2000年趋势fig = filter.visualize_hire_trends(1985, 2000)plt.show()# 示例4:统计信息stats = filter.analyze_hire_years()print("\n入职人数最多的年份:")peak_year = max(stats.items(), key=lambda x: x[1])print(f"{peak_year[0]}年: {peak_year[1]:,}人")finally:# 确保关闭连接filter.close()1986年入职员工(共36150人):
89812: Boriana Vingron (F) - 1986-01-01
90916: Eishiro Curless (M) - 1986-01-01
202218: Kristian Bergere (M) - 1986-01-01
213442: Vidar Tibblin (M) - 1986-01-01
214099: Holgard Prenel (F) - 1986-01-01
100711: Kwangyoen Rosca (M) - 1986-01-01
100812: Saniya Lanphier (F) - 1986-01-01
103944: Domenick Dehkordi (M) - 1986-01-01
104083: Basil Matteis (F) - 1986-01-01
104764: Juichirou Munke (M) - 1986-01-01
...(仅显示前10条,共36150条)员工入职年份统计报告
========================================1985年: 共35316人
示例员工:- 110022: Margareta Markovitch (M)- 110085: Ebru Alpin (M)- 110183: Shirish Ossenbruggen (F)- 110303: Krassimir Wegerle (F)- 110511: DeForest Hagimont (M)...(共35316人)1986年: 共36150人
示例员工:- 89812: Boriana Vingron (F)- 90916: Eishiro Curless (M)- 202218: Kristian Bergere (M)- 213442: Vidar Tibblin (M)- 214099: Holgard Prenel (F)...(共36150人)1987年: 共33501人
示例员工:- 417048: Subhada Bernardeschi (F)- 401034: Chikako Famili (F)- 422005: Avishai Stentiford (M)- 401863: Ghassan Wolniewicz (F)- 403068: Urs Matzat (M)...(共33501人)1988年: 共31436人
示例员工:- 475755: Radhakrishnan Coombs (F)- 477365: Heng Siksek (M)- 478116: Debatosh Piveteau (F)- 478374: Hironoby Furudate (F)- 482005: Kwangyoen Dahlbom (M)...(共31436人)1989年: 共28394人
示例员工:- 207103: Wonhee Byoun (F)- 211368: Alenka Zirintsis (F)- 211528: Kiam Schaar (M)- 100526: Huei Sewelson (M)- 213224: Godehard Demeyer (F)...(共28394人)1990年: 共25610人
示例员工:- 418290: Remzi Demizu (F)- 419042: Insup Rosar (F)- 419834: Mohit Hiroyama (F)- 421168: Kshitij Falco (F)- 430862: Hugo Uchoa (M)...(共25610人)入职人数最多的年份:
1986年: 36,150人