In [1]:
import matplotlib as mpl
mpl.rcParams['animation.embed_limit'] = 50.0 # Increase the limit to 50MB
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import os
from IPython.display import display, HTML
# Configuration
DATA_FILE = r"C:\Users\11\Desktop\Python\Python Project\Trade_China_2000-2023\trade_data_updated.xlsx"
YEAR_START = 2000
YEAR_END = 2023
OUTPUT_GIF = r"C:\Users\11\PycharmProjects\PythonProject2\china_trade_animation_smooth.gif"
FPS = 7 # Frames per second for saving the GIF
TRANSITION_STEPS = 7 # Number of intermediate frames for smooth transitions
MIN_VALUE = 1 # Avoids zero values in visualization
# Modern professional colors
COLORS = {
"Asia": "#1f77b4", # Blue
"Africa": "#ff7f0e", # Orange
"Europe": "#2ca02c", # Green
"Latin America": "#d62728", # Red
"Oceania and Pacific Islands": "#9467bd", # Purple
"North America": "#8c564b" # Brown
}
BACKGROUND_COLOR = "#f4f4f4" # Light grey background
# Standard continent order based on dataset
STANDARD_CONTINENTS = list(COLORS.keys())
# Rest of the code...
from IPython.display import display, HTML
def hide_code_toggle():
display(HTML('''
<style>
.code-toggle {
position: fixed;
top: 20px;
left: 20px;
z-index: 1000;
padding: 10px 17px;
background: #1570FF; /* Bright blue color */
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
transition: all 0.3s ease;
}
.code-toggle:hover {
background: #1976D2;
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
transform: translateY(-1px);
}
.code-toggle:active {
transform: translateY(1px);
}
.code-container {
margin: 10px 0;
transition: all 0.3s ease;
}
</style>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$(document).ready(function() {
// Create toggle button
var toggleButton = $('<button class="code-toggle">â–¼ Toggle Code</button>');
$('body').prepend(toggleButton);
// Wrap all code blocks in containers
$('pre').each(function() {
$(this).wrap('<div class="code-container"></div>');
});
// Set initial state
var codeVisible = true;
// Toggle functionality
toggleButton.click(function() {
codeVisible = !codeVisible;
$('.code-container').slideToggle(300);
$(this).html(codeVisible ? 'â–¼ Hide Code' : 'â–² Show Code');
$(this).toggleClass('code-hidden', !codeVisible);
});
});
</script>
'''))
hide_code_toggle()
def clean_continent_names(df):
"""Ensure continent names match the dataset's standard format."""
continent_map = {
"Asia": "Asia",
"Africa": "Africa",
"Europe": "Europe",
"Latin America": "Latin America",
"Oceania and Pacific Islands": "Oceania and Pacific Islands",
"North America": "North America",
}
if 'Continent' in df.columns:
df['Continent'] = df['Continent'].astype(str).str.strip()
# Remove non-breaking spaces and normalize whitespace
df['Continent'] = df['Continent'].str.replace(r'\s+', ' ', regex=True)
# Apply mapping
df['Continent'] = df['Continent'].map(continent_map).fillna('Other')
else:
raise KeyError("Missing 'Continent' column in the dataset.")
return df
def load_and_prepare_data():
"""Load and process the trade data."""
print("Loading and preparing data...")
try:
# Load data
df = pd.read_excel(DATA_FILE)
# Clean continent names
df = clean_continent_names(df)
# Check for the trade type column
if 'Unnamed: 0' not in df.columns:
raise KeyError("Missing required column 'Unnamed: 0' (Import/Export labels)")
# Rename the column for clarity
df = df.rename(columns={'Unnamed: 0': 'TradeType'})
# Find year columns
year_columns = [year for year in range(YEAR_START, YEAR_END + 1) if year in df.columns]
if not year_columns:
# Fallback to string names if needed
year_columns = [str(year) for year in range(YEAR_START, YEAR_END + 1) if str(year) in df.columns]
if not year_columns:
raise KeyError("Year columns not found in the dataset.")
# Melt the DataFrame so that years become rows
df = df.melt(id_vars=['TradeType', 'Continent'], value_vars=year_columns,
var_name='Year', value_name='TradeVolume')
# Convert Year to integer if necessary
df['Year'] = df['Year'].astype(int)
# Ensure TradeVolume is numeric
df['TradeVolume'] = pd.to_numeric(df['TradeVolume'], errors='coerce').fillna(0)
# Split into imports and exports
imports = df[df['TradeType'].str.strip() == 'Import']
exports = df[df['TradeType'].str.strip() == 'Export']
if imports.empty or exports.empty:
raise ValueError("Missing Import/Export data")
# Group by continent and year
import_agg = imports.groupby(['Continent', 'Year'], as_index=False)['TradeVolume'].sum()
export_agg = exports.groupby(['Continent', 'Year'], as_index=False)['TradeVolume'].sum()
print("Data successfully processed\n")
return import_agg, export_agg
except Exception as e:
print(f"Error: {str(e)}")
return None, None
def create_histogram(ax, data, year, title, y_max_limit, is_interpolated=False):
"""Create histogram plots for trade volume per continent."""
ax.clear()
ax.set_facecolor(BACKGROUND_COLOR)
ax.set_ylim(0, y_max_limit)
ax.set_title(title, fontsize=16, fontweight="bold")
ax.set_xlabel('') # Remove the x-axis label (i.e., "Continent")
ax.set_ylabel('Trade Volume')
# If data is already interpolated, use it directly; otherwise, filter by exact year.
if not is_interpolated:
year_data = data[data['Year'] == year]
else:
year_data = data
if year_data.empty:
ax.text(0.5, 0.5, "No Data Available", ha='center', va='center', color='red', fontsize=12)
return
# Group data by continent and ensure all standard continents appear (fill missing ones with 0)
grouped = year_data.groupby('Continent', as_index=False)['TradeVolume'].sum()
grouped = grouped.set_index('Continent').reindex(STANDARD_CONTINENTS, fill_value=0).reset_index()
# Plot the bar chart
ax.bar(grouped['Continent'], grouped['TradeVolume'],
color=[COLORS.get(c, '#999999') for c in grouped['Continent']], edgecolor='black')
# Add text labels above the bars
for i, row in grouped.iterrows():
ax.text(i, row['TradeVolume'] + 0.05, f"{row['TradeVolume']:,.0f}", ha='center', va='bottom', fontsize=9)
# Set x-ticks and labels with rotation and alignment
ax.set_xticks(range(len(STANDARD_CONTINENTS)))
ax.set_xticklabels(STANDARD_CONTINENTS, rotation=45, ha='right')
ax.grid(True, axis='y', linestyle='--', alpha=0.6)
def main():
import_data, export_data = load_and_prepare_data()
if import_data is None or export_data is None:
print("Failed to load data. Exiting.")
return
# Get the unique years from the data
years = sorted(import_data['Year'].unique())
print(f"Years available in the dataset: {years}")
if not years:
print("No years found in the dataset. Exiting.")
return
# Find the maximum trade volume from both imports and exports for consistent y-axis scaling
max_trade_volume = max(import_data['TradeVolume'].max(), export_data['TradeVolume'].max())
y_max_limit = max_trade_volume * 1.1 # Add some padding for readability
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(18, 10))
plt.subplots_adjust(top=0.88, bottom=0.15) # Adjusted bottom margin for x-labels
# Set y-axis labels and limits
ax1.set_ylabel('Trade Volume')
ax2.set_ylabel('Trade Volume')
ax1.set_ylim(0, y_max_limit)
ax2.set_ylim(0, y_max_limit)
# Remove redundant x-tick configuration here
# Calculate total frames accounting for intermediate steps (Step 3)
total_frames = (len(years) - 1) * TRANSITION_STEPS + 1
def update(frame):
# Determine which two years to interpolate between
index = frame // TRANSITION_STEPS
fraction = (frame % TRANSITION_STEPS) / TRANSITION_STEPS
if index >= len(years) - 1:
# Last frame: use the final year's data
current_year = years[-1]
displayed_year = current_year
data_import_frame = import_data[import_data['Year'] == current_year]
data_export_frame = export_data[export_data['Year'] == current_year]
else:
year_current = years[index]
year_next = years[index + 1]
displayed_year = year_current # Interpolated display value
def interpolate_data(data, current_year, next_year, fraction):
interp_dict = {'Continent': [], 'TradeVolume': []}
for continent in STANDARD_CONTINENTS:
# Get current year value
vol_current = data[(data['Year'] == current_year) & (data['Continent'] == continent)]
vol_current = vol_current['TradeVolume'].iloc[0] if not vol_current.empty else 0
# Get next year value
vol_next = data[(data['Year'] == next_year) & (data['Continent'] == continent)]
vol_next = vol_next['TradeVolume'].iloc[0] if not vol_next.empty else 0
# Linear interpolation
interp_volume = (1 - fraction) * vol_current + fraction * vol_next
interp_dict['Continent'].append(continent)
interp_dict['TradeVolume'].append(interp_volume)
return pd.DataFrame(interp_dict)
data_import_frame = interpolate_data(import_data, year_current, year_next, fraction)
data_export_frame = interpolate_data(export_data, year_current, year_next, fraction)
# Update the overall title with the interpolated year value
fig.suptitle(f"China's Trade in Continents(In millions$) - {displayed_year}", fontsize=18, fontweight="bold", y=0.95)
# Use the modified create_histogram with the interpolated data flag set to True
create_histogram(ax1, data_import_frame, displayed_year, "Imports", y_max_limit, is_interpolated=True)
create_histogram(ax2, data_export_frame, displayed_year, "Exports", y_max_limit, is_interpolated=True)
print("Generating smooth animation...")
ani = FuncAnimation(fig, update, frames=total_frames, interval=100, blit=False)
# Display the animation in the notebook
display(HTML(ani.to_jshtml()))
# Close the figure to prevent extra figures from being displayed
# Ensure the output directory exists
output_dir = os.path.dirname(OUTPUT_GIF)
if output_dir and not os.path.exists(output_dir):
print(f"Warning: Output directory does not exist. Creating it.")
os.makedirs(output_dir)
try:
ani.save(OUTPUT_GIF, writer='pillow', fps=FPS, dpi=60, progress_callback=None)
print(f"Success! Saved as {OUTPUT_GIF}")
except Exception as e:
print(f"GIF Save Error: {str(e)}")
finally:
plt.close(fig)
if __name__ == "__main__":
main()
Loading and preparing data... Data successfully processed Years available in the dataset: [2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023] Generating smooth animation...