
Contents
What is expected goals?
Expected goals (xG) is a statistic that helps measure the quality of scoring chances in a football match. Rather than just counting goals, xG looks at how likely each shot was to score, based on factors like shot location, type, and defensive pressure. The goal is to give a better idea of a team’s performance and attacking effectiveness.
How xG works
Each shot in a match is assigned an xG value between 0 and 1. For example:
– A shot from close range with minimal defensive pressure might have an xG of 0.80 (or an 80% chance of scoring).
– A long-range effort with defenders blocking might be assigned an xG of 0.05.
The sum of all xG values for a team in a match gives an overall expectation of how many goals they should have scored, based on the quality of their chances.
Factors influencing xG
The calculation of xG includes variables such as:
– Distance from goal
– Angle of the shot
– Type of assist (e.g., through ball, cross)
– Body part used (e.g., header, foot)
– Defensive pressure
– Position of the goalkeeper
Why xG matters
xG is important for:
– Analysts who want to understand a team’s performance beyond just the final score.
– Coaches and scouts who use it to spot players who are performing better or worse than expected.
– Fans and pundits who use it to make more informed assessments of matches.
– Fantasy football platforms and betting markets that use xG for deeper insights.
By comparing actual goals to expected goals, xG shows if a team was efficient, missed chances, or was just unlucky.
What is Elixir?
Elixir is a programming language used to create scalable, reliable, and easy-to-maintain applications. It is built on the Erlang Virtual Machine (BEAM), which is known for powering systems in telecoms and distributed applications.
Key features of Elixir:
– Concurrency: Elixir can handle thousands of tasks at once using lightweight processes, making it great for real-time applications like chat systems, notifications, or live match updates.
– Fault tolerance: Elixir follows Erlang’s “let it crash” approach, allowing applications to recover from errors without crashing the whole system.
– Scalability: Elixir can handle anything from a few users to millions, and it can scale across multiple machines or processors.
– Clean syntax: Inspired by Ruby, Elixir has simple and easy-to-read code, making it user-friendly for developers.
– Ecosystem: Elixir has powerful tools like Phoenix (a web framework), LiveView (for real-time features), and Mix (a build tool), helping developers create fast and interactive applications with less effort.
Where Elixir excels
While Elixir is commonly used in web development, it’s also a strong choice for:
– Real-time dashboards
– Event-driven systems
– APIs and backend services
– Messaging systems
In this guide, we’ll use Elixir to connect with Sportmonks’ football API, parse match data, particularly Expected goals (xG) and structure it for real-world applications.
Chapter 1. Setting up your environment
Before we begin building with Elixir and integrating Sportmonks’ Football API, we need to prepare our development environment. This includes installing Elixir, adding essential libraries, and generating an API token from Sportmonks.
1. Install Elixir
If you haven’t already installed Elixir, follow the steps below based on your operating system:
macOS (via Homebrew)
brew install elixir
Ubuntu / Debian
sudo apt update sudo apt install elixir
Windows
Use the precompiled installer available from the official Elixir website.
You can verify the installation by running:
elixir --version
2. Create a new Elixir project
We’ll use Elixir’s build tool, Mix, to create a new project:
mix new sportmonks_xg --module SportmonksXG cd sportmonks_xg
3. Add required dependencies
Open mix.exs and add the following libraries under the deps function:
defp deps do [ {:httpoison, "~> 1.8"}, # For making HTTP requests {:jason, "~> 1.4"} # For parsing JSON responses ] end
Then run:
mix deps.get
4. Get your Sportmonks API token
To access the Football API, you’ll need an API token:
– Go to my.sportmonks.com.
– Register or log in.
– Navigate to the “API Tokens” section.
– Copy your personal token.
Store this token securely as we’ll use it in upcoming API calls.
5. Set up environment variables (optional but recommended)
To avoid hardcoding the API token, you can load it via environment variables. In your project root, create a .env file:
touch .env
Add the following line:
SPORTMONKS_API_TOKEN=your_token_here
Use the dotenv library or System.get_env(“SPORTMONKS_API_TOKEN”) in your app to access it.
6. Set up an IDE for Elixir development
Having a good development environment improves productivity, debugging, and code readability. Here are some recommended IDE options for Elixir development:
VS Code is a lightweight and flexible editor with excellent Elixir support.
Steps to set up:
1. Install VS code:: Download it here
2. Install the ElixirLS extension:
– Open VS Code.
– Go to the Extensions tab (Ctrl+Shift+X or Cmd+Shift+X on macOS).
– Search for ElixirLS: Elixir support and debugger and install it.
3. Enable mix project support:
When opening your Elixir project folder, ElixirLS automatically detects it and compiles it in the background.
4. Optional: Install additional helpful extensions:
– DotENV for syntax highlighting in your .env file.
– Code Spell Checker useful when writing documentation
Chapter 2. Retrieving fixture data using Elixir
Now that your Elixir environment is ready, it’s time to connect to the Sportmonks football API and retrieve detailed match fixture data including participants, venue, league, state, scores, lineups, events, and expected goals (xG). This forms the foundation for building match visualisations, live dashboards, or performance insights.
1. Understand the fixture endpoint
We’ll use the following Sportmonks API endpoint:
GET https://api.sportmonks.com/v3/football/fixtures/{fixture_id}?include=participants;league;venue;state;scores;events.type;events.period;events.player;xGFixture;lineups.player;lineups.xGlineup;lineups.details
This is a fixture between Man Utd and Aston Villa in the English Premier League.
Replace {fixture_id} with the match ID you want to analyse eg 19135043.
The include parameter tells Sportmonks to return additional nested data in the same response.
2. Write the API request module in Elixir
In your Elixir project (e.g., sportmonks_xg/lib/sportmonks_xg.ex), create a module to fetch fixture data.
defmodule SportmonksXG do @moduledoc """ Fetches and displays fixture data from Sportmonks Football API. """ def api_token, do: System.get_env("SPORTMONKS_API_TOKEN") || "your_token" @base_url "https://api.sportmonks.com/v3/football/fixtures" def get_fixture_data(fixture_id) do includes = Enum.join([ "participants", "league", "venue", "state", "scores", "events.type", "events.period", "events.player", "xGFixture", "lineups.player", "lineups.xGlineup", "lineups.details" ], ";") url = "#{@base_url}/#{fixture_id}?api_token=#{api_token()}&include=#{includes}" case HTTPoison.get(url, [], recv_timeout: 10_000) do {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> Jason.decode(body) {:ok, %HTTPoison.Response{status_code: code}} -> {:error, "Request failed with status code #{code}"} {:error, %HTTPoison.Error{reason: reason}} -> {:error, "HTTP error: #{inspect(reason)}"} end end end
3. Test the fixture call
Create a small runner script (lib/demo.exs) or test it in IEx:
alias SportmonksXG case SportmonksXG.get_fixture_data(19135043) do {:ok, %{"data" => fixture}} -> IO.puts("Match: #{fixture["name"]}") IO.inspect(fixture["participants"], label: "Participants") IO.inspect(fixture["xgfixture"], label: "xG Stats") {:error, reason} -> IO.puts("Error: #{reason}") end
Then run:
iex -S mix
Or execute a one-off file:
mix run lib/demo.exs
4. What you’ll receive in the response
The response includes nested maps with data such as:
– participants: Home and away teams
– venue: Stadium and location info
– scores: First half, second half, and full time scores
– xGFixture: Expected goals per team
– lineups: Player information, xG per player
– events: Key match events (e.g. goals, cards, subs)
– state: Live, Finished, Postponed etc.
– league: Competition details
"xgfixture": [ { "id": 413065374, "fixture_id": 19135043, "type_id": 5304, "participant_id": 15, "data": { "value": 0.5302 }, "location": "away", "type": { "id": 5304, "name": "Expected Goals (xG)", "code": "expected-goals", "developer_name": "EXPECTED_GOALS", "model_type": "statistic", "stat_group": "offensive" } }, { "id": 413065373, "fixture_id": 19135043, "type_id": 5304, "participant_id": 14, "data": { "value": 2.8283 }, "location": "home", "type": { "id": 5304, "name": "Expected Goals (xG)", "code": "expected-goals", "developer_name": "EXPECTED_GOALS", "model_type": "statistic", "stat_group": "offensive" } },
Chapter 3. Working with expected goals (xG) data
Expected goals (xG) gives a clearer picture of how effective a team or player was during a match. With the Sportmonks API, xG data is available at both the fixture level and player level within the fixture.
In this chapter, I’ll show you how to parse that xG data using Elixir and turn it into useful outputs.
1. Understanding the xG data structure
When you include xgfixture, lineups.xglineup, and lineups.player in your request, the API will return:
– Fixture xG: Overall expected goals for each team.
– Player xG: Individual xG contributions, found within each team’s player list.
Example JSON paths:
{ "data": { ... "xgfixture": [ { "team_id": 1530, "value": 1.67 }, { "team_id": 1531, "value": 0.95 } ], "lineups": [ { "team_id": 1530, "players": [ { "player": { "display_name": "Mason Mount" }, "xglineup": [ { "value": 0.50 } ] } ] } ] } }
2. Extract and print team xG in Elixir
Let’s write a helper function to format the xGFixture data:
def print_team_xg(%{"xgfixture" => xg_data, "participants" => participants}) do Enum.each(xg_data, fn %{"team_id" => team_id, "value" => value} -> team = Enum.find(participants, fn %{"id" => id} -> id == team_id end) team_name = team["name"] IO.puts("#{team_name} xG: #{value}") end) end
Usage:
{:ok, %{"data" => fixture}} = SportmonksXG.get_fixture_data(19135043) print_team_xg(fixture)
3. Extract and display player-level xG
To explore xG per player:
def print_player_xg(%{"lineups" => lineups}) do Enum.each(lineups, fn lineup -> team_id = lineup["team_id"] players = lineup["players"] || [] IO.puts("Team ID: #{team_id} Player xG stats:") Enum.each(players, fn %{"player" => player, "xglineup" => [xg | _]} -> name = player["display_name"] IO.puts("- #{name}: xG #{xg["value"]}") end) end) end
This function shows a breakdown of how much xG each player contributed.
4. What you can build with this
With this xG data, you can:
– Create xG dashboards for tactical analysis.
– Compare real goals vs expected to identify under/overperformers.
– Enrich fantasy football platforms with deeper player stats.
– Build coaching or scouting tools with player efficiency insights.
Chapter 4. Retrieving xG data for a team across a date range
If you’re building a tool to analyse team performance over time, one match isn’t enough. You need trends, and that’s where the fixtures/between endpoint becomes powerful. With it, you can retrieve all fixtures for a team within a specific date range, then extract and compare expected goals (xG) data across those matches.
1. What the endpoint does
Sportmonks provides the following endpoint:
GET https://api.sportmonks.com/v3/football/fixtures/between/{start_date}/{end_date}/{team_id}
– start_date: Format YYYY-MM-DD (beginning of the range).
– end_date: Format YYYY-MM-DD (end of the range)
– team_id: The team you want to analyse.
This returns all matches that the team played within the date range.
2. Why it’s useful for xG analysis
By adding xgfixture as an include, you’ll get the expected goals values per team for every fixture. You can then:
– Visualise a team’s attacking performance trend
– Compare actual goals vs xG over time
– Identify matches where the team overperformed or underperformed
3. Fetching and parsing with Elixir
Here’s how to implement this in your project:
a) Function to fetch fixtures
def get_team_xg_trend(start_date, end_date, team_id) do includes = "xgfixture" url = "https://api.sportmonks.com/v3/football/fixtures/between/#{start_date}/#{end_date}/#{team_id}?api_token=#{@api_token}&include=#{includes}" case HTTPoison.get(url) do {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> Jason.decode(body) {:ok, %HTTPoison.Response{status_code: code}} -> {:error, "Failed with status code #{code}"} {:error, %HTTPoison.Error{reason: reason}} -> {:error, "HTTP Error: #{inspect(reason)}"} end end
b) Display xG values per match
def print_team_xg_trend(fixtures, team_id) do Enum.each(fixtures, fn fixture -> xg_data = fixture["xgfixture"] || [] date = fixture["starting_at"] case Enum.find(xg_data, fn xg -> xg["team_id"] == team_id end) do %{"value" => value} -> IO.puts("[#{date}] xG: #{value}") _ -> IO.puts("[#{date}] xG data not available") end end) end
Example usage:
{:ok, %{"data" => fixtures}} = SportmonksXG.get_team_xg_trend("2024-08-01", "2024-12-01", 53) print_team_xg_trend(fixtures, 53)
Example output
[2024-08-02] xG: 1.45 [2024-08-08] xG: 0.88 [2024-08-15] xG: 2.10
You can take this a step further and:
– Calculate average xG per match
– Plot xG trends using a graphing library or external tools
– Compare xG vs actual goals for efficiency analysis
This gives you quantitative insight into how threatening a team has been across a series of matches beyond just final scores. It’s especially useful for scouting, predictive modelling, or fan-driven analytics dashboards.
Power your Elixir app with xG data from Sportmonks
Bring deeper football intelligence into your Elixir projects by integrating Sportmonks’ football API. With access to advanced metrics like expected goals (xG), you can analyse match performance, player efficiency, and tactical trends all from real-time, structured data.
Whether you’re building analytics dashboards, scouting tools, or fantasy platforms, Sportmonks makes it easy to access and use xG data directly in your Elixir application.
Start your free trial today and explore football like never before.