Published
Edited
May 31, 2022
Insert cell
# [Project](https://github.com/odu-cs725-infovis/public/blob/main/spr22/project.md) - CS 725/825, Spring 2022
Name: Adeniran Adeniyi <br/>
Title: English Premier League Recruiting/Scouting Dashboard<br/>
Description: https://observablehq.com/@aaden001/project-report-cs-725-825-spring-2022
Due: April 12th, 2022
Insert cell
Insert cell
viewof playerName = select(player.name) //distinctPremierLeague distinctFootballdata
Insert cell
Insert cell
Insert cell
viewof positionSelect = select(["Forwards","MidFielders", "Defenders","GK"])
Insert cell
viewof category = select(["Very Poor","Poor", "Average","Excellent"]) //Select from the percentage category of players based on overall performace in position metric.
Insert cell
viewof features = select(["Pace", "Shooting", "Dribbling", "Defending", "GoalKeeping","Passing","Physical"])
Insert cell
top50perpentile.view()
Insert cell
Insert cell
Insert cell
viewof football = aq.fromCSV(await FileAttachment("data.csv").text())
.view(1000
)
Insert cell
Insert cell
vl.markBar()
.data(football)
.encode(
vl.x().fieldQ('Age').bin({ extent: [0, 50], maxbins: 50 }).axis({ format: 'd' }).title("Distribution of Age of players"),
vl.y().count().axis({ orient: 'right' }).axis({ })
)
.width(width)
.height(200)
.config({ view: { stroke: null }})
.render()
Insert cell
Insert cell
viewof playerOnfield = aq.fromCSV(await FileAttachment("england-premier-league-players-2018-to-2019-stats.csv").text())
.view(1000
)
Insert cell
streamLined = football.select('ID','Name','Age','Nationality', 'Club', "Position",
'Crossing', 'Finishing','HeadingAccuracy','ShortPassing','Positioning', 'Volleys',//Attacking
'Dribbling','Curve', 'FKAccuracy', 'LongPassing', 'BallControl', // Skill
'Acceleration', 'SprintSpeed', 'Agility', 'Reactions', 'Balance', // Movement
'ShotPower', 'Jumping','Stamina','Strength', 'LongShots', //Power
'Aggression', 'Interceptions', 'Positioning', 'Vision','Penalties','Composure',//Mentality
'Marking', 'StandingTackle', 'SlidingTackle', //Defending
'GKDiving', 'GKHandling', 'GKKicking', 'GKPositioning', 'GKReflexes'// Goal Keeping
).reify()
Insert cell
//streamLinedOnfield = playerOnfield.select('full_name', 'age','position','minutes_played_overall', 'apperances_overall','goals_overall','assists_overall','clean_sheets_overall',)
Insert cell
streamLined.view()
Insert cell
Insert cell
computedStats = streamLined.derive({
"Pace": d=> ((d['Acceleration'] + d['SprintSpeed'])/2),
"Shooting": d => ((d['Finishing'] + d['LongShots']+d['Penalties'] +d['Positioning'] + d['ShotPower'] + d['Volleys'])/6),
"Passing": d => ((d['Crossing'] + d['Curve']+ d['FKAccuracy']+d['LongPassing'] +d["ShortPassing"]+d['Vision'] )/6),
"Dribbling": d => ((d['Agility'] + d['Balance']+ d['BallControl']+d['Composure'] +d["Dribbling"]+d['Reactions'] )/6),
"Defending": d => ((d['HeadingAccuracy'] + d['Interceptions']+ d['Marking']+d['SlidingTackle'] +d["StandingTackle"] )/5),
"Physical": d => ((d['Aggression'] + d['Jumping']+ d['Stamina']+d['Strength'])/4),
"GoalKeeping": d => ((d['GKDiving'] + d['GKHandling']+ d['GKKicking']+d['GKPositioning'] +d["GKReflexes"]+d['SprintSpeed'] )/6)
}).derive({"avgCapability": d=> (( d["Pace"] + d["Shooting"]+d["Passing"] + d["Defending"] + d["Physical"] + d["GoalKeeping"] +d["Dribbling"])/7),})
Insert cell
computedStats.view()
Insert cell
Insert cell
distinctFootballdata = football.rollup({
club: d => op.array_agg_distinct(d["Club"]) ,// array of unique values for Club
nation: d => op.array_agg_distinct(d["Nationality"]), // array of unique values for Club
position: d => op.array_agg_distinct(d["Position"])
})
.objects()[0]
Insert cell
distinctPremierLeague = playerOnfield.rollup({
club: d => op.array_agg_distinct(d["Current Club"]) ,// array of unique values for Club
})
.objects()[0]
Insert cell
distinctFootballdata.nation
Insert cell
//viewof clubchoice = select(distinctPremierLeague.club) //distinctPremierLeague distinctFootballdata
Insert cell
selectedClub = computedStats
.filter(aq.escape(d => d['Club'] === clubchoice))
.reify()

Insert cell
experiment = selectedClub.reify()
Insert cell
experiment.columnNames()
Insert cell
experiment.objects()
Insert cell
plotEx = experiment.rollup({values: d => op.array_agg(op.row_object("Name", "Dribbling", "Pace", "Shooting", "GoalKeeping", "Passing"))}).objects()
Insert cell
selectedClub.view()
Insert cell
selectedClub.numRows() ///Total number of players in the team
Insert cell
PostionGraph = selectedClub.groupby('Position').count({as: 'Total Count'}).orderby(aq.desc(d => d["Total Count"])).reify() ///Sort in descending order
Insert cell
PostionGraph.view()
Insert cell
Insert cell
vl.markBar({tooltip: true})
.data(PostionGraph)
.encode(
vl.x().fieldQ("Total Count").title("Number of Players").axis({grid: true,tickCount: 5}),
vl.y().fieldN("Position").sort("-x"),
)
.width(width/2)
.height(200)
.padding("1")
.render()
Insert cell
Insert cell
teamMetric = selectedClub.select('Pace','Dribbling','Shooting', 'Passing','Defending', 'Physical', 'GoalKeeping')
Insert cell
teamMetric.view()
Insert cell
teamMetricCalculated = teamMetric.rollup({
avgPace: d => op.mean(d["Pace"]),
stdPace: d => op.stdev(d["Pace"]),
avgDribble: d => op.mean(d["Dribbling"]) ,// The average score for dribble performance in the club
stdDribble: d => op.stdev(d["Dribbling"]), // The standard deviabtion for dribble performance in the club
avgShooting: d => op.mean(d["Shooting"]),
stdShooting: d => op.stdev(d["Shooting"]),
avgPassing: d => op.mean(d["Passing"]),
stdPassing: d => op.stdev(d["Passing"]),

avgDefending: d => op.mean(d["Defending"]),
stdDefending: d => op.stdev(d["Defending"]),

avgPhysical: d => op.mean(d["Physical"]),
stdPhysical: d=> op.stdev(d["Physical"]),

avgGoalKeeping: d => op.mean(d["GoalKeeping"]),
stdGoalKeeping: d =>op.stdev(d['GoalKeeping'])
}).reify()

Insert cell
teamMetricCalculated.view()
Insert cell
converting it to an object
Insert cell
TMCObject = teamMetricCalculated.object()
Insert cell
teamMetricCalculated.object().avgDribble
Insert cell
Insert cell
teamAnalysisZscore = selectedClub.derive(
{
"PaceZscore" : aq.escape(d =>( d["Pace"] - TMCObject.avgPace)/TMCObject.stdPace),
"DribblingZscore" : aq.escape(d =>( d["Dribbling"] - TMCObject.avgDribble)/TMCObject.stdDribble),
"ShootingZscore" : aq.escape(d =>( d["Shooting"] - TMCObject.avgShooting)/TMCObject.stdShooting),
"PassingZscore" : aq.escape(d =>( d["Passing"] - TMCObject.avgPassing)/TMCObject.stdPassing),
"DefendingZscore" : aq.escape(d =>( d["Defending"] - TMCObject.avgDefending)/TMCObject.stdDefending),
"PhysicalZscore" : aq.escape(d =>( d["Physical"] - TMCObject.avgPhysical)/TMCObject.stdPhysical),
"GoalKeepingZscore" : aq.escape(d =>( d["GoalKeeping"] - TMCObject.avgGoalKeeping)/TMCObject.stdGoalKeeping),
}
).select("Name","PaceZscore", "DribblingZscore", "ShootingZscore", "PassingZscore", "DefendingZscore","PhysicalZscore", "GoalKeepingZscore").reify()
Insert cell
teamAnalysisZscore.view()
Insert cell
Insert cell
vl.markBar({tooltip: true})
.data(selectedClub)
.encode(
vl.x().fieldQ('Age').bin({ extent: [0, 50], maxbins: 50 }).axis({ format: 'd' }).title("Distribution of Age of players"),
vl.y().count().axis({ orient: 'right' }).axis({ })
)
.width(width)
.height(200)
.config({ view: { stroke: null }})
.render()
Insert cell
Insert cell
Insert cell
Insert cell
https://www.fifplay.com/encyclopedia/defender/
Insert cell
distinctFootballdata.position
Insert cell
This gives the image position of all forwards
<img width="400" height="300" src="https://www.fifplay.com/img/public/position-forward.jpg">
</img>
Insert cell
Forwards = ["ST","LW", "LF", "CF", "RF", "RW","LS","RS"]
Insert cell
This gives the image position of mid-fielders
<img width="400" height="300" src="https://www.fifplay.com/img/public/position-midfielder.jpg">
</img>
Insert cell
MidFielders = ["CM","RM", "LM","CDM","CAM", "RDM", "RAM","LAM","LDM","LCM","RCM"]
Insert cell
Insert cell
Defenders = [
"CB","LB", "LWB", "RWB","RB","LCB","RCB"
]
Insert cell
GK = ["GK"]
Insert cell
otherClubs = computedStats.filter(aq.escape(d => d['Club'] != clubchoice))
Insert cell
otherClubs.view(1000)
Insert cell
Insert cell
otherClubs.numRows()
Insert cell
Insert cell
positionSelect
Insert cell
Insert cell
function SelectPositionVariable(pos, Forwards,MidFielders,Defenders,GK){
if(pos === "Forwards"){
return Forwards;
}else if(pos==="MidFielders"){
return MidFielders;
}else if(pos ==="GK"){
return GK;
}
return Defenders;

}
Insert cell
positionSet = new Set(SelectPositionVariable(positionSelect,Forwards,MidFielders,Defenders,GK))
Insert cell
filtedByPosition = otherClubs
.filter(aq.escape(d => positionSet.has(d["Position"])))
.reify()
Insert cell
filtedByPosition.view()
Insert cell
filtedByPosition.numRows()
Insert cell
Insert cell
vl.markBar({tooltip: true})
.data(filtedByPosition)
.encode(
vl.x().fieldQ('Age').title("Distribution of Age of Picked players"),
vl.y().count().axis({ orient: 'right' }).axis({ })
)
.width(width/2)
.height(400)
.config({ view: { stroke: null }})
.padding(70)
.render()
Insert cell
pickFromCompute = filtedByPosition.rollup({
avgPace: d => op.mean(d["Pace"]),
stdPace: d => op.stdev(d["Pace"]),
avgDribble: d => op.mean(d["Dribbling"]) ,// The average score for dribble performance in the club
stdDribble: d => op.stdev(d["Dribbling"]), // The standard deviabtion for dribble performance in the club
avgShooting: d => op.mean(d["Shooting"]),
stdShooting: d => op.stdev(d["Shooting"]),
avgPassing: d => op.mean(d["Passing"]),
stdPassing: d => op.stdev(d["Passing"]),

avgDefending: d => op.mean(d["Defending"]),
stdDefending: d => op.stdev(d["Defending"]),

avgPhysical: d => op.mean(d["Physical"]),
stdPhysical: d=> op.stdev(d["Physical"]),

avgGoalKeeping: d => op.mean(d["GoalKeeping"]),
stdGoalKeeping: d =>op.stdev(d['GoalKeeping'])
}).reify()
Insert cell
pickFromCompute.view()
Insert cell
picKFrombject = pickFromCompute.object()
Insert cell
pickFromAnalysisZscore = filtedByPosition.derive(
{
"PaceZscore" : aq.escape(d =>( d["Pace"] - picKFrombject.avgPace)/picKFrombject.stdPace),
"DribblingZscore" : aq.escape(d =>( d["Dribbling"] - picKFrombject.avgDribble)/picKFrombject.stdDribble),
"ShootingZscore" : aq.escape(d =>( d["Shooting"] - picKFrombject.avgShooting)/picKFrombject.stdShooting),
"PassingZscore" : aq.escape(d =>( d["Passing"] - picKFrombject.avgPassing)/picKFrombject.stdPassing),
"DefendingZscore" : aq.escape(d =>( d["Defending"] - picKFrombject.avgDefending)/picKFrombject.stdDefending),
"PhysicalZscore" : aq.escape(d =>( d["Physical"] - picKFrombject.avgPhysical)/picKFrombject.stdPhysical),
"GoalKeepingZscore" : aq.escape(d =>( d["GoalKeeping"] - picKFrombject.avgGoalKeeping)/picKFrombject.stdGoalKeeping),
}
).select("Name","Age","Position","Club","Pace","Dribbling","Shooting","Defending","GoalKeeping","Physical","PaceZscore", "DribblingZscore", "ShootingZscore", "PassingZscore", "DefendingZscore","PhysicalZscore", "GoalKeepingZscore","avgCapability")
.derive({
"meanZscore": aq.escape(d=> ((d['PaceZscore'] + d['DribblingZscore']+d['ShootingZscore'] + d['PassingZscore']+ d['DefendingZscore'] + d['GoalKeepingZscore'] + d["PhysicalZscore"])/6).toPrecision(4)),
}).orderby("meanZscore") //aq.desc("meanZscore") "meanZscore"
.reify()
Insert cell
pickFromAnalysisZscore.view()
Insert cell
totalSelected = pickFromAnalysisZscore.numRows()
Insert cell
Insert cell
percentRank = pickFromAnalysisZscore.derive({
"rank": op.rank(d=> d["meanZscore"])
}).reify()
Insert cell
descPercentRank = percentRank.derive({
"percentile": aq.escape(d => d["rank"]/totalSelected)
}).orderby(aq.desc("meanZscore")).reify()
Insert cell
descPercentRank.view()
Insert cell
# Range based Picking
Insert cell
//viewof category = select(["Very Poor","Poor", "Average","Excellent"])
Insert cell
category
Insert cell
percentInterval =pickPlayerCategory(category)
Insert cell
outputPercentileName = (category == "Excellent"? "Excellent": category)
Insert cell
function pickPlayerCategory(category){
var rangeObj = { start: 0, finish: .25};
if(category ==="Poor"){
rangeObj.finish =.50;
rangeObj.start =.25;
}
else if(category === "Average"){
rangeObj.finish =.75;
rangeObj.start =.50;
}else if(category === "Excellent"){
rangeObj.finish =1.00;
rangeObj.start =.75;
}
return rangeObj;
}
Insert cell
percentRankCategory = descPercentRank
.filter(aq.escape(d =>(d["percentile"] > percentInterval.start)))
.filter(aq.escape(d =>(d["percentile"] <=percentInterval.finish))).reify()
Insert cell
top50perpentile = percentRankCategory.slice(0,50).reify() /// so much people, used this to get the top 50 player of each percentile
Insert cell
top50perpentile.view()
Insert cell
pickFromAnalysisZscore.numRows()
Insert cell
PostionGraph = selectedClub.groupby('Position').count({as: 'Total Count'}).orderby(aq.desc(d => d["Total Count"])).reify() ///Sort in descending order
Insert cell
Insert cell
player = selectedClub.rollup({
name: d => op.array_agg_distinct(d["Name"]) ,// array of unique values for Club
postion: d => op.array_agg_distinct(d["Position"]) // array of unique values for Club
})
.objects()[0]
Insert cell
playerMainInfor = selectedClub
.filter( aq.escape(d => d['Name'] === playerName))
.select("Age", "Position")
.reify()
Insert cell
playerMainInfor2 = selectedClub
.filter( aq.escape(d => d['Name'] === playerName))
.select("Crossing", "Finishing", "HeadingAccuracy", "ShortPassing","Positioning","Volleys", "Curve","FKAccuracy","LongPassing", "BallControl", "Acceleration", "SprintSpeed", "Agility", "Reactions", "Balance", "ShotPower", "Jumping", "Stamina", "Strength", "LongShots", "Aggression","Interceptions","Vision", "Penalties", "Composure", "Marking", "StandingTackle", "SlidingTackle","GKDiving", "GKHandling", "GKKicking", "GKPositioning", "GKReflexes")
.reify()
Insert cell
Insert cell
playerMainInfor2.view()
Insert cell
playerBasicInfor = playerMainInfor.object()
Insert cell
playerAge = playerBasicInfor.Age
Insert cell
playerPosition =playerBasicInfor.Position
Insert cell
//viewof playerName = select(player.name) //distinctPremierLeague distinctFootballdata
Insert cell
vl.markBar({tooltip: true})
.data(zScorePlot)
.encode(
vl.x().fieldN("Attribute"),
vl.y().fieldQ("Zscores"),
)
.width(width/3)
.height(400)
.render()
Insert cell
zScorePlot =
aq.fromArrow(
aq.toArrow(
formatZFunction(
teamAnalysisZscore
.filter(aq.escape(d => d["Name"] === playerName))
.select("PaceZscore", "DribblingZscore", "ShootingZscore", "PassingZscore", "DefendingZscore","PhysicalZscore", "GoalKeepingZscore")
.object()
)
)
)
Insert cell
formatZFunction(
teamAnalysisZscore
.filter(aq.escape(d => d["Name"] === playerName))
.select("PaceZscore", "DribblingZscore", "ShootingZscore", "PassingZscore", "DefendingZscore","PhysicalZscore", "GoalKeepingZscore")
.object()
)
Insert cell
zScorePlot.view()
Insert cell
Insert cell
formatZFunction = obj => {
const keys = Object.keys(obj);
const res = [];
for(let i = 0; i < keys.length; i++){
res.push({
'Attribute': keys[i],
'Zscores': obj[keys[i]]
});
};
return res;
};
Insert cell
{

const brush = vl.selectInterval().encodings('x');
const attributeSelect = vl.selectSingle().encodings("x");
const ageDistribution = vl.markBar({tooltip: true})
.data(percentRankCategory)
.params(brush)
.encode(
vl.x().fieldQ('Age').title("Distribution of Age of Other "+ outputPercentileName+" Picked players" ),
vl.y().count().axis({ orient: 'right' }).axis({ })
//vl.color().value('steelblue')
)
.width(width/2.3)
.height(400)
.config({ view: { stroke: null }})
.padding(70);

const zscoreTop50players = vl.markBar({tooltip: true})
.data(top50perpentile)
.encode(
vl.x().fieldN('Name').title("Names of players in" ),
vl.y().fieldQ('meanZscore'),
vl.tooltip(['Name', 'meanZscore']),
//vl.tooltip().fieldN(['meanZscore', 'Name']),
vl.opacity().if(brush, vl.value(0.75)).value(0.05)
).select(attributeSelect).encode(
vl.color().if(attributeSelect, vl.color().value("steelblue")).value("lightgrey")
)
.width(width/2.3)
.height(250);

const lineChartTurnedPoint = vl.layer
(vl.markCircle()
.data(top50perpentile)
.params(vl.selectInterval().bind("scales"))
.encode(
vl.x().fieldQ("avgCapability").scale({"domain": [0,100]}),
vl.y().fieldQ(features).scale({"domain": [0,100]}),
vl.tooltip().fieldN(['Name'])
).transform(
vl.filter(attributeSelect)
)
,
vl.markCircle()
.params(vl.selectInterval().bind("scales"))
.data(selectedClub)
.encode(
vl.x().fieldQ("avgCapability").scale({"domain": [0,100]}),
vl.y().fieldQ(features).scale({"domain": [0,100]}),
vl.color(
{
// The first condition that is met applies.
condition: [
{ //param: "datum['Age'] === 20", oneOf: false, value: "salmon"
value: "red",
test:
{
field: "Name",
oneOf: [playerName]
}
},
],
// Default color.
value: "lightgrey",
}
),
vl.tooltip().fieldN(['Name'])
)
)
.width(width * 0.6).height(150)
//.render()
return vl.vconcat(vl.vconcat(ageDistribution,zscoreTop50players),lineChartTurnedPoint).render();
}
Insert cell
/**newtable = aq.fromJSON(teamAnalysisZscore
.filter(aq.escape(d => d["Name"] === playerName))
.select("PaceZscore", "DribblingZscore", "ShootingZscore", "PassingZscore", "DefendingZscore", "GoalKeepingZscore")
.objects())**/
Insert cell
///newtable.view()
Insert cell
Insert cell
sampleFormat = [
{"key": "pace", "value": 19, "category": 0},
{"key": "shooting", "value": 22, "category": 0},
{"key": "passing", "value": 14, "category": 0},
{"key": "dribbling", "value": 38, "category": 0},
{"key": "defending", "value": 50, "category": 0},
{"key": "physical", "value": 5, "category": 0},
{"key": "goalkeeping", "value": 27, "category": 0},
{"key": "pace", "value": 12, "category": 1},
{"key": "shooting", "value": 42, "category": 1},
{"key": "passing", "value": 13, "category": 1},
{"key": "dribbling", "value": 6, "category": 1},
{"key": "defending", "value": 15, "category": 1},
{"key": "physical", "value": 8, "category": 1},
{"key": "goalkeeping", "value": 27, "category": 1},
]
Insert cell
zscore = vl.markBar({tooltip: true})
.data(zScorePlot)
.encode(
vl.x().fieldN("Attribute"),
vl.y().fieldQ("Zscores"),
)
.width(width/3)
.height(400);
Insert cell
Insert cell
sameple
Insert cell
{
return vl.vconcat(sameple,zscore)
.spacing(5)
.render();
}
Insert cell
# Appendix
Insert cell
import { aq, op} from '@uwdata/arquero'
Insert cell
import {select} from "@jashkenas/inputs"
Insert cell
import {vl} from '@vega/vega-lite-api-v5'
Insert cell
embed = require("vega-embed")
Insert cell
# References


1.https://www.includehelp.com/code-snippets/how-to-access-an-object-having-spaces-in-the-objects-key-using-javascript.aspx <br/>
2.https://uwdata.github.io/arquero/api/#loadArrow <br/>
3.https://observablehq.com/@mahog/vega-tutorial-1-lets-make-a-bar-chart <br/>
4.https://stackoverflow.com/questions/68805413/how-do-i-highlight-specific-bar-s-using-vega-lite <br/>
5.https://www.youtube.com/watch?v=WTB6peagZR8 <br/>
6.https://observablehq.com/@uwdata/interaction <br/>
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more