Spaces:
Running
Running
| import gradio as gr | |
| import pandas as pd | |
| import plotly.express as px | |
| import plotly.graph_objects as go | |
| import numpy as np | |
| import math | |
| import random | |
| # Load and preprocess data | |
| df = pd.read_csv('Hurun_Global_Rich_List_2025_COORD.csv') | |
| df['Wealth $B'] = pd.to_numeric(df['Wealth $B'], errors='coerce') | |
| df = df.dropna(subset=['lat', 'lon']) | |
| df['Log Wealth'] = np.log10(df['Wealth $B']) | |
| # Ensure "Age" is an integer | |
| df['Age'] = pd.to_numeric(df['Age'], errors='coerce').astype('Int64') | |
| # Extract country from location | |
| df['Country'] = df['Location'].str.split(',').str[-1].str.strip() | |
| # Add jitter to coordinates to prevent overlapping | |
| def add_jitter(coords, jitter_range=0.2): | |
| """Add random jitter to coordinates to prevent overlapping markers""" | |
| coord_counts = {} | |
| jittered_coords = [] | |
| for coord in coords: | |
| rounded = (round(coord[0], 2), round(coord[1], 2)) | |
| count = coord_counts.get(rounded, 0) | |
| coord_counts[rounded] = count + 1 | |
| if count > 0: | |
| angle = 2 * math.pi * (count / 8) | |
| distance = jitter_range * min(0.5 + count/10, 1.5) | |
| jitter_lat = math.sin(angle) * distance | |
| jitter_lon = math.cos(angle) * distance | |
| jittered_coords.append((coord[0] + jitter_lat, coord[1] + jitter_lon)) | |
| else: | |
| jittered_coords.append(coord) | |
| return jittered_coords | |
| # Create logarithmic scale for visualization | |
| def create_globe(): | |
| df['Size'] = df['Log Wealth'] * 8 | |
| df['Color Value'] = df['Log Wealth'] | |
| coords = list(zip(df['lat'], df['lon'])) | |
| jittered_coords = add_jitter(coords) | |
| df['lat_jitter'] = [c[0] for c in jittered_coords] | |
| df['lon_jitter'] = [c[1] for c in jittered_coords] | |
| min_log = df['Log Wealth'].min() | |
| max_log = df['Log Wealth'].max() | |
| wealth_ticks = [1, 10, 30, 100, 300, 1000] | |
| log_ticks = [math.log10(x) for x in wealth_ticks if x <= 10**max_log and x >= 10**min_log] | |
| tick_labels = [f"${x}B" for x in wealth_ticks if x <= 10**max_log and x >= 10**min_log] | |
| fig = go.Figure(go.Scattergeo( | |
| lon = df['lon_jitter'], | |
| lat = df['lat_jitter'], | |
| text = df.apply(lambda row: ( | |
| f"<b>{row['Name']}</b><br>" | |
| f"Rank: {row['Rank']}<br>" | |
| f"Wealth: ${row['Wealth $B']}B<br>" | |
| f"Enterprise: {row['Enterprise']}<br>" | |
| f"Sector: {row['Sector']}<br>" | |
| f"Age: {row['Age']}<br>" | |
| f"Location: {row['Location']}" | |
| ), axis=1), | |
| marker = dict( | |
| size = df['Size'], | |
| color = df['Color Value'], | |
| colorscale = 'Turbo', | |
| opacity = 0.7, | |
| line_color='rgb(40,40,40)', | |
| line_width=0.5, | |
| sizemode = 'diameter', | |
| colorbar = dict( | |
| title="Wealth (Billion USD)", | |
| thickness=15, | |
| len=0.35, | |
| bgcolor='rgba(255,255,255,0.5)', | |
| tickvals = log_ticks, | |
| ticktext = tick_labels | |
| ) | |
| ), | |
| hoverinfo = 'text', | |
| name = '' | |
| )) | |
| fig.update_geos( | |
| projection_type="orthographic", | |
| showcoastlines=True, | |
| coastlinecolor="RebeccaPurple", | |
| showland=True, | |
| landcolor="rgb(243, 243, 243)", | |
| showocean=True, | |
| oceancolor="rgb(217, 244, 252)" | |
| ) | |
| fig.update_layout( | |
| height=500, | |
| margin={"r":0,"t":0,"l":0,"b":0}, | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| geo=dict(bgcolor='rgba(0,0,0,0)') | |
| ) | |
| return fig | |
| # Create distribution plots | |
| def create_age_plot(): | |
| fig = px.histogram(df, x='Age', nbins=20, | |
| title='Age Distribution', | |
| color_discrete_sequence=['#636EFA']) | |
| fig.update_layout(height=300) | |
| return fig | |
| def create_country_plot(): | |
| top_countries = df['Country'].value_counts().head(10) | |
| fig = px.bar(top_countries, | |
| title='Top 10 Countries', | |
| labels={'value': 'Count', 'index': 'Country'}, | |
| color=top_countries.index, | |
| color_discrete_sequence=px.colors.qualitative.Pastel) | |
| fig.update_layout(height=300, showlegend=False) | |
| return fig | |
| def create_gender_plot(): | |
| gender_counts = df['Sex'].value_counts() | |
| fig = px.pie(gender_counts, | |
| names=gender_counts.index, | |
| values=gender_counts.values, | |
| title='Gender Distribution', | |
| hole=0.4) | |
| fig.update_layout(height=300) | |
| return fig | |
| def create_sector_plot(): | |
| top_sectors = df['Sector'].value_counts().head(10) | |
| fig = px.bar(top_sectors, | |
| title='Top 10 Sectors', | |
| labels={'value': 'Count', 'index': 'Sector'}, | |
| color=top_sectors.index, | |
| color_discrete_sequence=px.colors.qualitative.Pastel) | |
| fig.update_layout(height=300, showlegend=False) | |
| return fig | |
| # Create display dataframe without extra columns | |
| display_columns = ['Rank', 'Rank change', 'Wealth $B', 'Enterprise', 'Sector', | |
| 'Name', 'Sex', 'Age', 'Sidekick', 'Location'] | |
| display_df = df[display_columns].sort_values('Rank').reset_index(drop=True) | |
| # Function to update table based on search and sort | |
| def update_table(search_term, sort_column, sort_order): | |
| temp_df = df[display_columns].copy() | |
| # Filter based on search term | |
| if search_term: | |
| search_term = search_term.lower() | |
| temp_df = temp_df[temp_df.apply(lambda row: search_term in ' '.join(map(str, row)).lower(), axis=1)] | |
| # Sort based on selected column and order | |
| if sort_column: | |
| ascending = (sort_order == "Ascending") | |
| temp_df = temp_df.sort_values(by=sort_column, ascending=ascending) | |
| return temp_df.reset_index(drop=True) | |
| # Create Gradio interface | |
| with gr.Blocks(theme='SebastianBravo/simci_css', css="#my_dataframe th { font-size: 12px !important; } #my_dataframe td { font-size: 10px !important; }") as demo: | |
| gr.Markdown("# Global Billionaires Dashboard 2025") | |
| gr.Markdown("Interactive visualisation of the world's wealthiest individuals") | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("### π Global Wealth Distribution") | |
| gr.Markdown("Circle size and color intensity both represent wealth on a logarithmic scale") | |
| gr.Markdown("Nearby points are slightly randomized to prevent overlapping") | |
| globe = gr.Plot(create_globe(), show_label=False) | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("### π Distribution Analysis") | |
| with gr.Row(): | |
| with gr.Column(): | |
| age_plot = gr.Plot(create_age_plot(), show_label=False) | |
| with gr.Column(): | |
| country_plot = gr.Plot(create_country_plot(), show_label=False) | |
| with gr.Row(): | |
| with gr.Column(): | |
| gender_plot = gr.Plot(create_gender_plot(), show_label=False) | |
| with gr.Column(): | |
| sector_plot = gr.Plot(create_sector_plot(), show_label=False) | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("### π Full Billionaire Data") | |
| # Add search and sort controls | |
| with gr.Row(): | |
| search_box = gr.Textbox(label="Search", placeholder="Type to search") | |
| with gr.Column(): | |
| sort_dropdown = gr.Dropdown(choices=display_columns, label="Sort by", value="Rank") | |
| sort_order = gr.Dropdown(choices=["Ascending", "Descending"], label="Sort order", value="Ascending") | |
| # Dataframe with custom elem_id | |
| data_table = gr.Dataframe( | |
| value=display_df, | |
| interactive=False, | |
| wrap=True, | |
| col_count=(len(display_columns), "fixed"), | |
| elem_id="my_dataframe" | |
| ) | |
| # Set up event listeners for search and sort | |
| search_box.change(update_table, inputs=[search_box, sort_dropdown, sort_order], outputs=data_table) | |
| sort_dropdown.change(update_table, inputs=[search_box, sort_dropdown, sort_order], outputs=data_table) | |
| sort_order.change(update_table, inputs=[search_box, sort_dropdown, sort_order], outputs=data_table) | |
| demo.launch() | |