uv add openvolley-pydatavolley
# data = dv.read_dv(path_of_dvw_file)
data = dv.read_dv(dv.example_file())
print(data)
Will return (this is a sample and not the entire example file)
[
{
"match_id": "106859",
"video_time": 495,
"code": "a02RM-~~~58AM~~00B",
"team": "University of Dayton",
"player_number": 2,
"player_name": "Maura Collins",
"player_id": "-230138",
"skill": "Reception",
"skill_type": "Jump-float serve reception",
"skill_subtype": "Jump Float",
"evaluation_code": "-",
"setter_position": "6",
"attack_code": null,
"set_code": null,
"set_type": null,
"start_zone": "5",
"end_zone": "8",
"end_subzone": "A",
"num_players_numeric": null,
"home_team_score": "0",
"visiting_team_score": "0",
"home_setter_position": "1",
"visiting_setter_position": "6",
"custom_code": "00B",
"home_p1": "19",
"home_p2": "9",
"home_p3": "11",
"home_p4": "15",
"home_p5": "10",
"home_p6": "7",
"visiting_p1": "1",
"visiting_p2": "16",
"visiting_p3": "17",
"visiting_p4": "10",
"visiting_p5": "6",
"visiting_p6": "8",
"start_coordinate": "0431",
"mid_coordinate": "-1-1",
"end_coordinate": "7642",
"point_phase": "Reception",
"attack_phase": null,
"start_coordinate_x": 1.26875,
"start_coordinate_y": 0.092596,
"mid_coordinate_x": null,
"mid_coordinate_y": null,
"end_coordinate_x": 1.68125,
"end_coordinate_y": 5.425924,
"set_number": "1",
"home_team": "University of Louisville",
"visiting_team": "University of Dayton",
"home_team_id": 17,
"visiting_team_id": 42,
"point_won_by": "University of Louisville",
"serving_team": "University of Louisville",
"receiving_team": "University of Dayton",
"rally_number": 1,
"possession_number": 1
}
]
A Python package for parsing and analyzing volleyball scouting data from DataVolley files (*.dvw).
Rebuilt pydatavolley with modern Python tooling (Astral ecosystem) for improved experience: UV for package management, Ruff for linting/formatting and Ty for type checking.
# Start a new project
uv init my-analysis && cd my-analysis
uv add openvolley-pydatavolley[plot]
# Load and analyze data
uv run python -c "
import datavolley as dv
data = dv.read_dv('match.dvw')
print(f'Loaded {len(data)} plays')
"
This package works best with uv - a fast Python package manager.
# macOS and Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
See uv documentation for more details.
If you have uv installed (recommended):
# Create a new project
uv init my-volleyball-analysis
cd my-volleyball-analysis
# Add the package
uv add openvolley-pydatavolley
# Plotting: Add plotting dependencies
uv add openvolley-pydatavolley[plot]
Create main.py
:
import datavolley as dv
# Load a match
data = dv.read_dv('path/to/match.dvw')
# data = dv.read_dv(dv.example_file())
# Access play data
for play in data[:5]:
print(f"{play['skill']}: {play['player_name']} - {play['evaluation_code']}")
# Filter by skill
serves = [p for p in data if p.get('skill') == 'Serve']
attacks = [p for p in data if p.get('skill') == 'Attack']
print(f"Total serves: {len(serves)}")
print(f"Total attacks: {len(attacks)}")
Run your analysis:
uv run main.py
Convert to DataFrame for easier analysis:
import datavolley as dv
import pandas as pd
data = dv.read_dv('match.dvw')
df = pd.DataFrame(data)
# Analyze attacks
attacks = df[df['skill'] == 'Attack']
print(attacks.groupby('player_name')['evaluation_code'].value_counts())
Or with Polars:
import datavolley as dv
import polars as pl
data = dv.read_dv('match.dvw')
df = pl.DataFrame(data)
# Fast filtering and aggregation
attacks = df.filter(pl.col('skill') == 'Attack')
print(attacks.group_by('player_name').agg(pl.len()))
If you prefer pip:
pip install openvolley-pydatavolley
pip install openvolley-pydatavolley[plot] # With plotting
Want to contribute or modify the package? Here's how to set up a development environment:
-
Clone the repository:
git clone https://github.com/openvolley/py-datavolley.git cd py-datavolley
-
Install dependencies:
# UV automatically creates and manages virtual environments uv sync
-
Run the example:
uv run main.py
-
Run linting and formatting:
ruff check datavolley/ ruff format datavolley/ ty check datavolley/
Running uv run main.py
with the included example file will output:
Sample Play Data
[
{
"match_id": "106859",
"video_time": 495,
"code": "a02RM-~~~58AM~~00B",
"team": "University of Dayton",
"player_number": 2,
"player_name": "Maura Collins",
"player_id": "-230138",
"skill": "Reception",
"skill_type": "Jump-float serve reception",
"skill_subtype": "Jump Float",
"evaluation_code": "-",
"setter_position": "6",
"attack_code": null,
"set_code": null,
"set_type": null,
"start_zone": "5",
"end_zone": "8",
"end_subzone": "A",
"num_players_numeric": null,
"home_team_score": "0",
"visiting_team_score": "0",
"home_setter_position": "1",
"visiting_setter_position": "6",
"custom_code": "00B",
"home_p1": "19",
"home_p2": "9",
"home_p3": "11",
"home_p4": "15",
"home_p5": "10",
"home_p6": "7",
"visiting_p1": "1",
"visiting_p2": "16",
"visiting_p3": "17",
"visiting_p4": "10",
"visiting_p5": "6",
"visiting_p6": "8",
"start_coordinate": "0431",
"mid_coordinate": "-1-1",
"end_coordinate": "7642",
"point_phase": "Reception",
"attack_phase": null,
"start_coordinate_x": 1.26875,
"start_coordinate_y": 0.092596,
"mid_coordinate_x": null,
"mid_coordinate_y": null,
"end_coordinate_x": 1.68125,
"end_coordinate_y": 5.425924,
"set_number": "1",
"home_team": "University of Louisville",
"visiting_team": "University of Dayton",
"home_team_id": 17,
"visiting_team_id": 42,
"point_won_by": "University of Louisville",
"serving_team": "University of Louisville",
"receiving_team": "University of Dayton",
"rally_number": 1,
"possession_number": 1
}
]
The package includes plotting capabilities for visualizing volleyball match data on court diagrams.
Install with plotting dependencies:
uv add openvolley-pydatavolley[plot]
Or install dependencies separately:
uv add matplotlib numpy shapely
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import datavolley as dv
# Draw a basic court
ax = dv.dv_court(zones=True, zone_labels=True)
plt.savefig('court.png')
import datavolley as dv
# Load match data
data = dv.read_dv('match.dvw')
# Get attack landing coordinates
attacks = [p for p in data if p.get('skill') == 'Attack']
coords = [(p['end_coordinate_x'], p['end_coordinate_y'])
for p in attacks if p.get('end_coordinate_x')]
# Create heatmap
ax = dv.dv_heatmap(coords, zones=True)
plt.title(f'Attack Landing Zones (n={len(coords)})')
plt.savefig('attack_heatmap.png')
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import datavolley as dv
data = dv.read_dv(dv.example_file())
# Filter X5 attacks
attacks = [p for p in data
if p.get('skill') == 'Attack' and p.get('attack_code') == 'X5']
# Plot trajectories
fig, ax = plt.subplots(figsize=(6, 10))
dv.dv_court(zones=True, ax=ax)
for attack in attacks:
if all([attack.get('start_coordinate_x'), attack.get('end_coordinate_x')]):
# Plot start (red) and end (blue) points
ax.scatter(attack['start_coordinate_x'], attack['start_coordinate_y'],
color='red', s=50, alpha=0.6)
ax.scatter(attack['end_coordinate_x'], attack['end_coordinate_y'],
color='blue', s=50, alpha=0.6)
# Draw trajectory line
ax.plot([attack['start_coordinate_x'], attack['end_coordinate_x']],
[attack['start_coordinate_y'], attack['end_coordinate_y']],
color='gray', linestyle='--', linewidth=1, alpha=0.5)
ax.set_title('X5 Attack Trajectories')
plt.savefig('x5_attacks.png', dpi=150)
![]() |
![]() |
X5 Attack Trajectories Shows ball path from set to landing |
Attack Code Comparison Compare patterns across attack types |
More examples available in the examples/
directory:
attack_analysis_complete.py
- Complete attack trajectory analysisattack_comparison.py
- Compare multiple attack codes side-by-side zonesattack_heatmap.py
- Heatmaps for attack set locations and landingteam_attacks.py
- Compare attack patterns between teams
See examples/README.md
for detailed documentation.
Please create an issue, fork and create a pull request.