第一步:数据加载与清洗

首先数据是一份 Excel 文件,我们需要预处理,包含了员工的打卡记录,包括 CheckIn(上班时间)和 CheckOut(下班时间)。:

df = pd.read_excel("employee_attendance.xlsx")
df['CheckIn'] = pd.to_datetime(df['CheckIn'], errors='coerce')
df['CheckOut'] = pd.to_datetime(df['CheckOut'], errors='coerce')
df.dropna(subset=['CheckIn', 'CheckOut'], inplace=True)

然后我计算了每位员工的每日工作时长,并提取了时间维度,比如周、月、星期几等,为后续分析做准备。


📈 第二步:可视化分析开始!

接下来,我开始用图表来“讲故事”。

1. 员工平均工作时长排名

首先,我绘制了每个员工的平均工作时长,看看谁是“加班狂魔”:

avg_work = df.groupby('Name')['Work Duration'].mean().sort_values(ascending=False)
plot_bar(avg_work, '员工平均工作时长排名', '员工姓名', '平均工作时长(小时)')

2. 总工作时长:谁是“劳模”?

统计了每个人的总工作时长,看看谁是真正的“劳模”:

total_work = df.groupby('Name')['Work Duration'].sum().sort_values(ascending=False)
plot_bar(total_work, '员工总工作时长排名', '员工姓名', '总工作时长(小时)')

图表显示,总时长与平均时长并不完全一致,说明有些人“靠时间”,有些人“靠效率”。

3. 每周 / 每月工作分布:堆叠条形图登场

为了进一步分析,用堆叠条形图展示了每位员工每周和每月的工作分布情况。这种图表非常适合观察趋势变化:

weekly_work = df.groupby(['Name', 'Week'])['Work Duration'].sum().unstack(fill_value=0)
plot_stacked_bar(weekly_work, '员工每周工作时长分布', '员工姓名', '总工作时长(小时)')

通过这个图,可以轻松发现某些员工在特定周次工作强度特别高,这可能与项目周期有关。

4. 工作趋势分析:折线图揭示变化

为了观察每位员工的工作趋势,绘制了每月工作时长的折线图:

monthly_trends = {name: group.groupby('Month')['Work Duration'].sum()
                  for name, group in df.groupby('Name')}
plot_line(monthly_trends, '员工每月工作时长趋势分析', '月份', '总工作时长(小时)')

有些员工在年初表现积极,到了年底反而“躺平”了,这也许能为 HR 提供一些管理上的启发。

5. 热力图:一图胜千言

热力图是我最喜欢的一种可视化方式,它能直观地展示二维数据的密集程度。分别绘制了每周和每月的工作热力图:

pivot_weekly = df.pivot_table(index='Name', columns='Week',
                              values='Work Duration', aggfunc='sum', fill_value=0)
plot_heatmap(pivot_weekly, '员工每周工作时长热力图', '周次', '员工姓名')

6. 饼图:工作日分布分析最后,我还用饼图展示了员工的工作日分布情况,看看大家更喜欢哪一天上班:weekday_counts = df['Weekday'].value_counts()
plt.pie(weekday_counts, autopct='%1.1f%%', startangle=90)

📌 如果你也想尝试你可以轻松复用我的代码,只需要:准备一份包含员工打卡时间的 Excel 文件;确保列名包含 CheckIn 和 CheckOut;运行代码,生成属于你的可视化分析报告!📥 获取完整代码如果你对这个项目感兴趣,可以复制下列完整代码和示例数据。也欢迎你分享自己的分析结果,我们一起探讨数据的魅力!

数据:DateEmployeeIDNameDepartmentCheckInCheckOut
2025/10/2E001张三技术部
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from matplotlib.ticker import FuncFormatter

# 设置全局样式和字体
plt.style.use('seaborn-v0_8-whitegrid')
sns.set_palette("husl")  # 使用更协调的配色方案
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['font.size'] = 12
plt.rcParams['axes.titlesize'] = 16
plt.rcParams['axes.labelsize'] = 14
plt.rcParams['xtick.labelsize'] = 12
plt.rcParams['ytick.labelsize'] = 12
plt.rcParams['legend.fontsize'] = 12

def load_and_preprocess_data(file_path):
    """加载并预处理数据"""
    df = pd.read_excel(file_path)
    df['CheckIn'] = pd.to_datetime(df['CheckIn'], errors='coerce')
    df['CheckOut'] = pd.to_datetime(df['CheckOut'], errors='coerce')
    df.dropna(subset=['CheckIn', 'CheckOut'], inplace=True)
    
    # 计算工作时长
    df['Work Duration'] = (df['CheckOut'] - df['CheckIn']).dt.total_seconds() / 3600
    
    # 提取时间维度
    df['Week'] = df['CheckIn'].dt.isocalendar().week
    df['Month'] = df['CheckIn'].dt.month
    df['Weekday'] = df['CheckIn'].dt.day_name()
    
    return df

def plot_bar(data, title, xlabel, ylabel, color=None, figsize=(12, 6), add_values=True):
    """优化后的条形图绘制函数"""
    plt.figure(figsize=figsize, dpi=100)
    ax = data.plot(kind='bar', color=color, edgecolor='white', linewidth=0.7, zorder=2)
    
    # 添加数值标签
    if add_values:
        for p in ax.patches:
            height = p.get_height()
            ax.annotate(f'{height:.1f}',
                        (p.get_x() + p.get_width() / 2., height),
                        ha='center', va='center',
                        xytext=(0, 5),
                        textcoords='offset points',
                        fontsize=10,
                        color='dimgrey')
    
    plt.title(title, pad=20, fontweight='bold')
    plt.xlabel(xlabel, labelpad=10)
    plt.ylabel(ylabel, labelpad=10)
    plt.xticks(rotation=45, ha='right')
    plt.grid(axis='y', linestyle='--', alpha=0.7, zorder=1)
    plt.tight_layout()
    plt.show()

def plot_line(data_dict, title, xlabel, ylabel, figsize=(14, 7)):
    """优化后的折线图绘制函数"""
    plt.figure(figsize=figsize, dpi=100)
    
    # 获取颜色循环
    colors = sns.color_palette("husl", len(data_dict))
    
    for i, (name, series) in enumerate(data_dict.items()):
        plt.plot(series.index, series.values, 
                 marker='o', markersize=8, 
                 linewidth=2.5,
                 color=colors[i],
                 label=name,
                 zorder=3)
    
    plt.title(title, pad=20, fontweight='bold')
    plt.xlabel(xlabel, labelpad=10)
    plt.ylabel(ylabel, labelpad=10)
    
    # 优化图例
    plt.legend(title='员工', 
               bbox_to_anchor=(1.02, 1), 
               loc='upper left',
               frameon=True,
               framealpha=0.9,
               edgecolor='white')
    
    # 添加网格
    plt.grid(True, linestyle='--', alpha=0.6, zorder=1)
    
    # 优化坐标轴
    plt.gca().spines['top'].set_visible(False)
    plt.gca().spines['right'].set_visible(False)
    
    plt.tight_layout()
    plt.show()

def plot_heatmap(pivot_table, title, xlabel, ylabel, figsize=(14, 8)):
    """优化后的热力图绘制函数"""
    plt.figure(figsize=figsize, dpi=100)
    
    # 创建自定义颜色映射
    cmap = sns.diverging_palette(220, 20, as_cmap=True)
    
    ax = sns.heatmap(pivot_table, 
                     annot=True, 
                     fmt='.1f',
                     cmap=cmap,
                     linewidths=0.5,
                     linecolor='white',
                     cbar_kws={
                         'label': '工作时长(小时)',
                         'shrink': 0.8,
                         'format': '%.1f'
                     },
                     annot_kws={"size": 10, "color": "black"})
    
    plt.title(title, pad=20, fontweight='bold')
    plt.xlabel(xlabel, labelpad=10)
    plt.ylabel(ylabel, labelpad=10)
    
    # 优化坐标轴标签
    plt.xticks(rotation=45, ha='right')
    plt.yticks(rotation=0)
    
    # 调整颜色条位置
    cbar = ax.collections[0].colorbar
    cbar.ax.yaxis.label.set_size(12)
    
    plt.tight_layout()
    plt.show()

def plot_stacked_bar(data, title, xlabel, ylabel, figsize=(14, 7)):
    """优化后的堆叠条形图绘制函数"""
    plt.figure(figsize=figsize, dpi=100)
    ax = data.plot(kind='bar', stacked=True, 
                   edgecolor='white', linewidth=0.7,
                   zorder=2)
    
    plt.title(title, pad=20, fontweight='bold')
    plt.xlabel(xlabel, labelpad=10)
    plt.ylabel(ylabel, labelpad=10)
    plt.xticks(rotation=45, ha='right')
    
    # 优化图例
    plt.legend(title='周/月', 
               bbox_to_anchor=(1.02, 1), 
               loc='upper left',
               frameon=True,
               framealpha=0.9)
    
    # 添加网格
    plt.grid(axis='y', linestyle='--', alpha=0.7, zorder=1)
    
    # 优化坐标轴
    plt.gca().spines['top'].set_visible(False)
    plt.gca().spines['right'].set_visible(False)
    
    plt.tight_layout()
    plt.show()

# 主程序逻辑
if __name__ == '__main__':
    # 加载并处理数据
    df = load_and_preprocess_data("employee_attendance.xlsx")
    print(df.head())
    
    # 1. 每个员工的平均工作时长(优化条形图)
    avg_work = df.groupby('Name')['Work Duration'].mean().sort_values(ascending=False)
    plot_bar(avg_work, 
             '员工平均工作时长排名', 
             '员工姓名', 
             '平均工作时长(小时)',
             color=sns.color_palette("viridis", len(avg_work)),
             add_values=True)
    
    # 2. 每个员工的总工作时长(优化条形图)
    total_work = df.groupby('Name')['Work Duration'].sum().sort_values(ascending=False)
    plot_bar(total_work, 
             '员工总工作时长排名', 
             '员工姓名', 
             '总工作时长(小时)',
             color=sns.color_palette("plasma", len(total_work)),
             add_values=True)
    
    # 3. 每个员工每周的工作时长(优化堆叠条形图)
    weekly_work = df.groupby(['Name', 'Week'])['Work Duration'].sum().unstack(fill_value=0)
    plot_stacked_bar(weekly_work, 
                     '员工每周工作时长分布', 
                     '员工姓名', 
                     '总工作时长(小时)')
    
    # 4. 每个员工每月的工作时长(优化堆叠条形图)
    monthly_work = df.groupby(['Name', 'Month'])['Work Duration'].sum().unstack(fill_value=0)
    plot_stacked_bar(monthly_work, 
                     '员工每月工作时长分布', 
                     '员工姓名', 
                     '总工作时长(小时)')
    
    # 5. 每个员工每月工作趋势折线图(优化折线图)
    monthly_trends = {name: group.groupby('Month')['Work Duration'].sum()
                      for name, group in df.groupby('Name')}
    plot_line(monthly_trends, 
              '员工每月工作时长趋势分析', 
              '月份', 
              '总工作时长(小时)')
    
    # 6. 热力图:每周工作分布(优化热力图)
    pivot_weekly = df.pivot_table(index='Name', columns='Week',
                                  values='Work Duration', aggfunc='sum', fill_value=0)
    plot_heatmap(pivot_weekly, 
                 '员工每周工作时长热力图', 
                 '周次', 
                 '员工姓名')
    
    # 7. 热力图:每月工作分布(优化热力图)
    pivot_monthly = df.pivot_table(index='Name', columns='Month',
                                   values='Work Duration', aggfunc='sum', fill_value=0)
    plot_heatmap(pivot_monthly, 
                 '员工每月工作时长热力图', 
                 '月份', 
                 '员工姓名')
    
    # 新增:工作日分布分析(饼图)
    weekday_counts = df['Weekday'].value_counts()
    plt.figure(figsize=(10, 8), dpi=100)
    colors = sns.color_palette("pastel")[0:7]
    wedges, texts, autotexts = plt.pie(weekday_counts, 
                                      autopct='%1.1f%%',
                                      startangle=90,
                                      colors=colors,
                                      wedgeprops={'linewidth': 1, 'edgecolor': 'white'},
                                      textprops={'fontsize': 12})
    
    plt.title('工作日分布比例', pad=20, fontweight='bold', fontsize=16)
    plt.legend(wedges, weekday_counts.index,
              title="星期",
              loc="center left",
              bbox_to_anchor=(1, 0, 0.5, 1))
    
    # 美化百分比标签
    for autotext in autotexts:
        autotext.set_color('white')
        autotext.set_fontweight('bold')
    
    plt.tight_layout()
    plt.show()

案例2

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from matplotlib.ticker import FuncFormatter

# 设置全局样式和字体
plt.style.use('seaborn-v0_8-whitegrid')
sns.set_palette("husl")  # 使用更协调的配色方案
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['font.size'] = 12
plt.rcParams['axes.titlesize'] = 16
plt.rcParams['axes.labelsize'] = 14
plt.rcParams['xtick.labelsize'] = 12
plt.rcParams['ytick.labelsize'] = 12
plt.rcParams['legend.fontsize'] = 12

def load_and_preprocess_data(file_path):
    """加载并预处理数据"""
    df = pd.read_excel(file_path)
    df['CheckIn'] = pd.to_datetime(df['CheckIn'], errors='coerce')
    df['CheckOut'] = pd.to_datetime(df['CheckOut'], errors='coerce')
    df.dropna(subset=['CheckIn', 'CheckOut'], inplace=True)
    
    # 计算工作时长
    df['Work Duration'] = (df['CheckOut'] - df['CheckIn']).dt.total_seconds() / 3600
    
    # 提取时间维度
    df['Week'] = df['CheckIn'].dt.isocalendar().week
    df['Month'] = df['CheckIn'].dt.month
    df['Weekday'] = df['CheckIn'].dt.day_name()
    
    return df

def plot_bar(data, title, xlabel, ylabel, color=None, figsize=(12, 6), add_values=True):
    """优化后的条形图绘制函数"""
    plt.figure(figsize=figsize, dpi=100)
    ax = data.plot(kind='bar', color=color, edgecolor='white', linewidth=0.7, zorder=2)
    
    # 添加数值标签
    if add_values:
        for p in ax.patches:
            height = p.get_height()
            ax.annotate(f'{height:.1f}',
                        (p.get_x() + p.get_width() / 2., height),
                        ha='center', va='center',
                        xytext=(0, 5),
                        textcoords='offset points',
                        fontsize=10,
                        color='dimgrey')
    
    plt.title(title, pad=20, fontweight='bold')
    plt.xlabel(xlabel, labelpad=10)
    plt.ylabel(ylabel, labelpad=10)
    plt.xticks(rotation=45, ha='right')
    plt.grid(axis='y', linestyle='--', alpha=0.7, zorder=1)
    plt.tight_layout()
    plt.show()

def plot_line(data_dict, title, xlabel, ylabel, figsize=(14, 7)):
    """优化后的折线图绘制函数"""
    plt.figure(figsize=figsize, dpi=100)
    
    # 获取颜色循环
    colors = sns.color_palette("husl", len(data_dict))
    
    for i, (name, series) in enumerate(data_dict.items()):
        plt.plot(series.index, series.values, 
                 marker='o', markersize=8, 
                 linewidth=2.5,
                 color=colors[i],
                 label=name,
                 zorder=3)
    
    plt.title(title, pad=20, fontweight='bold')
    plt.xlabel(xlabel, labelpad=10)
    plt.ylabel(ylabel, labelpad=10)
    
    # 优化图例
    plt.legend(title='员工', 
               bbox_to_anchor=(1.02, 1), 
               loc='upper left',
               frameon=True,
               framealpha=0.9,
               edgecolor='white')
    
    # 添加网格
    plt.grid(True, linestyle='--', alpha=0.6, zorder=1)
    
    # 优化坐标轴
    plt.gca().spines['top'].set_visible(False)
    plt.gca().spines['right'].set_visible(False)
    
    plt.tight_layout()
    plt.show()

def plot_heatmap(pivot_table, title, xlabel, ylabel, figsize=(14, 8)):
    """优化后的热力图绘制函数"""
    plt.figure(figsize=figsize, dpi=100)
    
    # 创建自定义颜色映射
    cmap = sns.diverging_palette(220, 20, as_cmap=True)
    
    ax = sns.heatmap(pivot_table, 
                     annot=True, 
                     fmt='.1f',
                     cmap=cmap,
                     linewidths=0.5,
                     linecolor='white',
                     cbar_kws={
                         'label': '工作时长(小时)',
                         'shrink': 0.8,
                         'format': '%.1f'
                     },
                     annot_kws={"size": 10, "color": "black"})
    
    plt.title(title, pad=20, fontweight='bold')
    plt.xlabel(xlabel, labelpad=10)
    plt.ylabel(ylabel, labelpad=10)
    
    # 优化坐标轴标签
    plt.xticks(rotation=45, ha='right')
    plt.yticks(rotation=0)
    
    # 调整颜色条位置
    cbar = ax.collections[0].colorbar
    cbar.ax.yaxis.label.set_size(12)
    
    plt.tight_layout()
    plt.show()

def plot_stacked_bar(data, title, xlabel, ylabel, figsize=(14, 7)):
    """优化后的堆叠条形图绘制函数"""
    plt.figure(figsize=figsize, dpi=100)
    ax = data.plot(kind='bar', stacked=True, 
                   edgecolor='white', linewidth=0.7,
                   zorder=2)
    
    plt.title(title, pad=20, fontweight='bold')
    plt.xlabel(xlabel, labelpad=10)
    plt.ylabel(ylabel, labelpad=10)
    plt.xticks(rotation=45, ha='right')
    
    # 优化图例
    plt.legend(title='周/月', 
               bbox_to_anchor=(1.02, 1), 
               loc='upper left',
               frameon=True,
               framealpha=0.9)
    
    # 添加网格
    plt.grid(axis='y', linestyle='--', alpha=0.7, zorder=1)
    
    # 优化坐标轴
    plt.gca().spines['top'].set_visible(False)
    plt.gca().spines['right'].set_visible(False)
    
    plt.tight_layout()
    plt.show()

# 主程序逻辑
if __name__ == '__main__':
    # 加载并处理数据
    df = load_and_preprocess_data("employee_attendance.xlsx")
    print(df.head())
    
    # 1. 每个员工的平均工作时长(优化条形图)
    avg_work = df.groupby('Name')['Work Duration'].mean().sort_values(ascending=False)
    plot_bar(avg_work, 
             '员工平均工作时长排名', 
             '员工姓名', 
             '平均工作时长(小时)',
             color=sns.color_palette("viridis", len(avg_work)),
             add_values=True)
    
    # 2. 每个员工的总工作时长(优化条形图)
    total_work = df.groupby('Name')['Work Duration'].sum().sort_values(ascending=False)
    plot_bar(total_work, 
             '员工总工作时长排名', 
             '员工姓名', 
             '总工作时长(小时)',
             color=sns.color_palette("plasma", len(total_work)),
             add_values=True)
    
    # 3. 每个员工每周的工作时长(优化堆叠条形图)
    weekly_work = df.groupby(['Name', 'Week'])['Work Duration'].sum().unstack(fill_value=0)
    plot_stacked_bar(weekly_work, 
                     '员工每周工作时长分布', 
                     '员工姓名', 
                     '总工作时长(小时)')
    
    # 4. 每个员工每月的工作时长(优化堆叠条形图)
    monthly_work = df.groupby(['Name', 'Month'])['Work Duration'].sum().unstack(fill_value=0)
    plot_stacked_bar(monthly_work, 
                     '员工每月工作时长分布', 
                     '员工姓名', 
                     '总工作时长(小时)')
    
    # 5. 每个员工每月工作趋势折线图(优化折线图)
    monthly_trends = {name: group.groupby('Month')['Work Duration'].sum()
                      for name, group in df.groupby('Name')}
    plot_line(monthly_trends, 
              '员工每月工作时长趋势分析', 
              '月份', 
              '总工作时长(小时)')
    
    # 6. 热力图:每周工作分布(优化热力图)
    pivot_weekly = df.pivot_table(index='Name', columns='Week',
                                  values='Work Duration', aggfunc='sum', fill_value=0)
    plot_heatmap(pivot_weekly, 
                 '员工每周工作时长热力图', 
                 '周次', 
                 '员工姓名')
    
    # 7. 热力图:每月工作分布(优化热力图)
    pivot_monthly = df.pivot_table(index='Name', columns='Month',
                                   values='Work Duration', aggfunc='sum', fill_value=0)
    plot_heatmap(pivot_monthly, 
                 '员工每月工作时长热力图', 
                 '月份', 
                 '员工姓名')
    
    # 新增:工作日分布分析(饼图)
    weekday_counts = df['Weekday'].value_counts()
    plt.figure(figsize=(10, 8), dpi=100)
    colors = sns.color_palette("pastel")[0:7]
    wedges, texts, autotexts = plt.pie(weekday_counts, 
                                      autopct='%1.1f%%',
                                      startangle=90,
                                      colors=colors,
                                      wedgeprops={'linewidth': 1, 'edgecolor': 'white'},
                                      textprops={'fontsize': 12})
    
    plt.title('工作日分布比例', pad=20, fontweight='bold', fontsize=16)
    plt.legend(wedges, weekday_counts.index,
              title="星期",
              loc="center left",
              bbox_to_anchor=(1, 0, 0.5, 1))
    
    # 美化百分比标签
    for autotext in autotexts:
        autotext.set_color('white')
        autotext.set_fontweight('bold')
    
    plt.tight_layout()
    plt.show()