最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Fill background of Python Seaborn KDE plot - Stack Overflow

programmeradmin0浏览0评论

I am working on a basketball graphic that uses a KDE plot to graph made shots onto an image of a court that I made. The KDE plot itself looks pretty good, but as you can see by the attached image, the plot does not "fill" all the way to the bottom of the image, which ideally I would like to make that light gray color fill the entire court to make it look better.

Here's the following code I am using:

import json
import matplotlib.pyplot as plt
import seaborn as sns
from PIL import Image
import numpy as np

# Set width and height
shot_width_x = 800 # Determined by StatsAppClient
shot_height_y = 671 # Determined by StatsAppClient
court_width_x = 1920 # Determined by Court.png
court_height_y = 1610 # Determined by Court.png

# Load JSON data
with open('data1.json') as f:
    data = json.load(f)

# Function to extract shot positions and made status for a given team
def extract_shots(team_players):
    global shot_width_x, shot_height_y
    shot_positions = []
    shot_made = []
    for player in team_players:
        for shot in player['Shots']:
            x, y = map(float, shot['ShotPosition'].split(','))
            if (x, y) != (0, 0) and 0 <= x <= shot_width_x and 0 <= y <= shot_height_y: # Filter out invalid shots, and free throws
                # Flip the Y coordinate
                y = shot_height_y - y
                shot_positions.append((x, y))
                shot_made.append(shot['Made'])
            else:
                print(f"Invalid shot detected for player {player['PlayerName']} at position ({x}, {y})")
    return shot_positions, shot_made

def extract_team_names(data):
    team1 = data['Team1Name']
    team2 = data['Team2Name']
    return team1, team2

# Function to calculate shot percentages
def calculate_percentages(team_players):
    total_shots = 0
    made_threes = 0
    made_twos = 0
    made_frees = 0
    attempted_threes = 0
    attempted_twos = 0
    attempted_frees = 0

    for player in team_players:
        for shot in player['Shots']:
            total_shots += 1
            if shot['PointValue'] == 3:
                attempted_threes += 1
                if shot['Made']:
                    made_threes += 1
            elif shot['PointValue'] == 2:
                attempted_twos += 1
                if shot['Made']:
                    made_twos += 1
            elif shot['PointValue'] == 1:
                attempted_frees += 1
                if shot['Made']:
                    made_frees += 1

    three_percentage = (made_threes / attempted_threes * 100) if attempted_threes else 0
    two_percentage = (made_twos / attempted_twos * 100) if attempted_twos else 0
    free_percentage = (made_frees / attempted_frees * 100) if attempted_frees else 0

    return three_percentage, two_percentage, free_percentage

# Extract team names
team1_name, team2_name = extract_team_names(data)

# Extract shots for both teams
team1_shot_positions, team1_shot_made = extract_shots(data['Team1Players'])
team2_shot_positions, team2_shot_made = extract_shots(data['Team2Players'])

# Calculate percentages for both teams
team1_percentages = calculate_percentages(data['Team1Players'])
team2_percentages = calculate_percentages(data['Team2Players'])

# Function to create heatmap for a given team
def create_heatmap(shot_positions, shot_made, team_name, team_num, percentages, player_names=None):
    if not shot_positions or not shot_made:
        print(f"No shot data available for {team_name}. Skipping heatmap creation.")
        return

    # Load basketball court image and resize it to court_width_x, court_height_y
    court_img = Image.open('court-one-side-eagle.png')
    court_img = court_img.resize((court_width_x, court_height_y))
    court_width, court_height = court_img.size

    # Scale shot positions from shot_width_x, shot_height_y to court_width_x, court_height_y
    scaled_shot_positions = [(x * (court_width_x / shot_width_x), y * (court_height_y / shot_height_y)) for x, y in shot_positions]

    # Separate made and missed shots
    made_shot_positions = [pos for pos, made in zip(scaled_shot_positions, shot_made) if made]
    missed_shot_positions = [pos for pos, made in zip(scaled_shot_positions, shot_made) if not made]

    # Create plot with white background
    fig, ax = plt.subplots(figsize=(12, 8), dpi=200)
    ax.set_facecolor('white')
    fig.set_facecolor('white')

    # Display court image
    ax.imshow(court_img, extent=[0, court_width, 0, court_height])

    # Convert made shot positions to arrays for plotting
    if made_shot_positions:
        x_coords, y_coords = zip(*made_shot_positions)
    else:
        x_coords, y_coords = [], []

    # Create heatmap using KDE plot
    sns.kdeplot(
        x=x_coords,
        y=y_coords,
        cmap='inferno', # Using inferno colormap
        fill=True, # Fills the area under the KDE curve
        alpha=0.7, # Set transparency level
        levels=50, # Increase number of contour levels for smoother gradient, 50 is a good value
        thresh=0, # Set threshold to 0 to include all data points
        ax=ax, # Plot on the same axis
        bw_adjust=0.8, # Adjust bandwidth
        clip=((0, court_width_x), (0, court_height_y)), # Clip KDE plot to court dimensions
        gridsize=100 # Increase grid size for smoother plot
    )

    # Add small dots for shot locations, green if made, red if missed
    for (x, y), made in zip(scaled_shot_positions, shot_made):
        if made:
            ax.scatter(x, y, c='green', marker='o', s=50, edgecolors='black', linewidths=1, alpha=0.6)
        else:
            ax.scatter(x, y, c='red', marker='x', s=50, linewidths=1, alpha=0.6)

    # Set plot limits to match the court image
    ax.set_xlim(0, court_width_x)
    ax.set_ylim(0, court_height_y)

    # Remove axes
    ax.set_axis_off()

    # Add title to the heatmap
    ax.set_title(f'{team_name} Shot Heatmap', fontsize=16, color='black')

    # Add text for percentages below the graph
    three_percentage, two_percentage, free_percentage = percentages
    fig.text(0.5, 0.07, f'Three-Point %: {three_percentage:.2f}% | Two-Point %: {two_percentage:.2f}% | Free Throw %: {free_percentage:.2f}%', color='black', fontsize=16, ha='center')

    # Add player names below the percentages if provided
    if player_names:
        fig.text(0.5, 0.93, f'Players: {", ".join(player_names)}', color='black', fontsize=12, ha='center')

    # Save plot to a file
    plt.savefig(f'heatmap_{team_num}.png', bbox_inches='tight', dpi=200)
    #plt.show()
    plt.close()

# Function to create heatmap for individual players
def create_individual_heatmap(team_num, player_numbers):
    if team_num == 1:
        team_players = data['Team1Players']
        team_name = team1_name
    else:
        team_players = data['Team2Players']
        team_name = team2_name

    selected_players = [player for player in team_players if player['PlayerNumber'] in player_numbers]
    shot_positions, shot_made = extract_shots(selected_players)
    if not shot_positions or not shot_made:
        print(f"No shot data available for selected players in {team_name}. Skipping heatmap creation.")
        return

    percentages = calculate_percentages(selected_players)
    player_names = [player['PlayerName'] for player in selected_players]

    create_heatmap(shot_positions, shot_made, f'{team_name} Selected Players', f'Team{team_num}_Selected', percentages, player_names)

# Create heatmaps for both teams
#create_heatmap(team1_shot_positions, team1_shot_made, f'{team1_name}', "team1", team1_percentages)
#create_heatmap(team2_shot_positions, team2_shot_made, f'{team2_name}', "team2", team2_percentages)

# Create combined heatmap
combined_shot_positions = team1_shot_positions + team2_shot_positions
combined_shot_made = team1_shot_made + team2_shot_made
combined_percentages = calculate_percentages(data['Team1Players'] + data['Team2Players'])
create_heatmap(combined_shot_positions, combined_shot_made, f'{team1_name} and {team2_name} Combined', "Combined", combined_percentages)

# Example usage for individual players
#create_individual_heatmap(1, ["2", "4"]) # Team 1, players 2 and 4
#create_individual_heatmap(2, ["1", "3"]) # Team 2, players 1 and 3

[Edit] Pulled the data.json file and image, as this was not important to finding the final answer.

The plot works correctly if I have a shot that is near the half court line, but obviously that wouldn't happen often during most games.

发布评论

评论列表(0)

  1. 暂无评论