Data Visualization for Construction
Overview
Based on DDC methodology (Chapter 4.1), this skill provides comprehensive data visualization techniques for construction analytics. Visual insights drive better decisions - from cost breakdowns to schedule analysis.
Book Reference: "Аналитика данных и принятие решений" / "Data Analytics and Decision Making"
"Визуализация данных превращает сложные наборы данных в понятные графики, которые могут использоваться для принятия решений на всех уровнях проекта."
— DDC Book, Chapter 4.1
Quick Start
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# Load construction data
df = pd.read_excel("project_data.xlsx")
# Quick bar chart - volumes by category
fig, ax = plt.subplots(figsize=(10, 6))
df.groupby('Category')['Volume_m3'].sum().plot(kind='bar', ax=ax)
ax.set_title('Volume by Category')
ax.set_ylabel('Volume (m³)')
plt.tight_layout()
plt.savefig('volume_by_category.png', dpi=150)
plt.show()
Matplotlib Fundamentals
Basic Charts for Construction
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
def create_cost_breakdown_pie(df, cost_col='Cost', category_col='Category'):
"""Create pie chart for cost breakdown"""
costs = df.groupby(category_col)[cost_col].sum()
fig, ax = plt.subplots(figsize=(10, 8))
# Create pie with percentage labels
wedges, texts, autotexts = ax.pie(
costs.values,
labels=costs.index,
autopct='%1.1f%%',
startangle=90,
colors=plt.cm.Set3.colors
)
ax.set_title('Cost Breakdown by Category', fontsize=14, fontweight='bold')
# Add total in center
ax.text(0, 0, f'Total:\n${costs.sum():,.0f}',
ha='center', va='center', fontsize=12)
plt.tight_layout()
return fig
def create_volume_bar_chart(df, volume_col='Volume_m3', category_col='Category'):
"""Create horizontal bar chart for volumes"""
volumes = df.groupby(category_col)[volume_col].sum().sort_values()
fig, ax = plt.subplots(figsize=(10, 6))
bars = ax.barh(volumes.index, volumes.values, color='steelblue')
# Add value labels
for bar, value in zip(bars, volumes.values):
ax.text(value + volumes.max() * 0.01, bar.get_y() + bar.get_height()/2,
f'{value:,.0f} m³', va='center', fontsize=10)
ax.set_xlabel('Volume (m³)')
ax.set_title('Material Volumes by Category', fontsize=14, fontweight='bold')
ax.set_xlim(0, volumes.max() * 1.15)
plt.tight_layout()
return fig
def create_level_comparison(df, value_col='Volume_m3', level_col='Level'):
"""Create grouped bar chart comparing levels"""
pivot = df.pivot_table(
values=value_col,
index=level_col,
columns='Category',
aggfunc='sum',
fill_value=0
)
fig, ax = plt.subplots(figsize=(12, 6))
pivot.plot(kind='bar', ax=ax, width=0.8)
ax.set_xlabel('Building Level')
ax.set_ylabel('Volume (m³)')
ax.set_title('Volume Distribution by Level and Category', fontsize=14, fontweight='bold')
ax.legend(title='Category', bbox_to_anchor=(1.02, 1), loc='upper left')
plt.xticks(rotation=45)
plt.tight_layout()
return fig
Time Series Visualization
def create_progress_chart(df, date_col='Date', value_col='Cumulative_Progress'):
"""Create S-curve progress chart"""
df = df.sort_values(date_col)
fig, ax = plt.subplots(figsize=(12, 6))
# Actual progress
ax.plot(df[date_col], df[value_col],
'b-', linewidth=2, label='Actual Progress')
# Planned progress (if available)
if 'Planned_Progress' in df.columns:
ax.plot(df[date_col], df['Planned_Progress'],
'g--', linewidth=2, label='Planned Progress')
ax.fill_between(df[date_col], 0, df[value_col], alpha=0.3)
ax.set_xlabel('Date')
ax.set_ylabel('Progress (%)')
ax.set_title('Project S-Curve', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(True, alpha=0.3)
# Format dates
fig.autofmt_xdate()
plt.tight_layout()
return fig
def create_gantt_chart(df, start_col='Start', end_col='End', task_col='Task'):
"""Create simple Gantt chart"""
df = df.sort_values(start_col)
fig, ax = plt.subplots(figsize=(14, len(df) * 0.5 + 2))
# Plot each task as horizontal bar
for i, (_, row) in enumerate(df.iterrows()):
start = pd.to_datetime(row[start_col])
end = pd.to_datetime(row[end_col])
duration = (end - start).days
ax.barh(i, duration, left=start, height=0.6,
align='center', color='steelblue', alpha=0.8)
ax.set_yticks(range(len(df)))
ax.set_yticklabels(df[task_col])
ax.set_xlabel('Date')
ax.set_title('Project Schedule - Gantt Chart', fontsize=14, fontweight='bold')
ax.grid(axis='x', alpha=0.3)
fig.autofmt_xdate()
plt.tight_layout()
return fig
Seaborn for Statistical Visualization
Distribution Analysis
import seaborn as sns
def create_distribution_analysis(df, value_col='Volume_m3', category_col='Category'):
"""Create distribution plots for construction data"""
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 1. Histogram with KDE
sns.histplot(data=df, x=value_col, kde=True, ax=axes[0, 0])
axes[0, 0].set_title('Volume Distribution')
# 2. Box plot by category
sns.boxplot(data=df, x=category_col, y=value_col, ax=axes[0, 1])
axes[0, 1].set_xticklabels(axes[0, 1].get_xticklabels(), rotation=45)
axes[0, 1].set_title('Volume by Category')
# 3. Violin plot
sns.violinplot(data=df, x=category_col, y=value_col, ax=axes[1, 0])
axes[1, 0].set_xticklabels(axes[1, 0].get_xticklabels(), rotation=45)
axes[1, 0].set_title('Volume Distribution by Category')
# 4. Strip plot with jitter
sns.stripplot(data=df, x=category_col, y=value_col,
ax=axes[1, 1], alpha=0.5, jitter=True)
axes[1, 1].set_xticklabels(axes[1, 1].get_xticklabels(), rotation=45)
axes[1, 1].set_title('Individual Elements')
plt.tight_layout()
return fig
def create_correlation_heatmap(df, numeric_cols=None):
"""Create correlation heatmap for numeric columns"""
if numeric_cols is None:
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
corr_matrix = df[numeric_cols].corr()
fig, ax = plt.subplots(figsize=(10, 8))
sns.heatmap(corr_matrix,
annot=True,
cmap='RdYlBu_r',
center=0,
fmt='.2f',
square=True,
ax=ax)
ax.set_title('Correlation Matrix', fontsize=14, fontweight='bold')
plt.tight_layout()
return fig
Category Analysis
def create_category_summary(df, category_col='Category',
value_col='Volume_m3', cost_col='Cost'):
"""Create comprehensive category summary visualization"""
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 1. Count by category
category_counts = df[category_col].value_counts()
sns.barplot(x=category_counts.index, y=category_counts.values, ax=axes[0, 0])
axes[0, 0].set_title('Element Count by Category')
axes[0, 0].set_xticklabels(axes[0, 0].get_xticklabels(), rotation=45)
# 2. Total volume by category
volumes = df.groupby(category_col)[value_col].sum().sort_values(ascending=False)
sns.barplot(x=volumes.index, y=volumes.values, ax=axes[0, 1])
axes[0, 1].set_title('Total Volume by Category')
axes[0, 1].set_xticklabels(axes[0, 1].get_xticklabels(), rotation=45)
# 3. Average cost by category
if cost_col in df.columns:
avg_cost = df.groupby(category_col)[cost_col].mean().sort_values(ascending=False)
sns.barplot(x=avg_cost.index, y=avg_cost.values, ax=axes[1, 0])
axes[1, 0].set_title('Average Cost by Category')
axes[1, 0].set_xticklabels(axes[1, 0].get_xticklabels