JavaScript (Node.js), 465 bytes
(i,C)=>(p='https://codegolf.stackexchange.com',n=t=a=[],g=(u,c)=>require('https').get(u,r=>r.on('data',d=>i+=d,r.on('end',_=>c(r.headers.location)))))(p+'/users/'+i,l=>g(p+l,_=>g(s=i.match(/ht.+\/s[^"]+\d\/[^?]/)+0,l=>g(new URL(l,s)+'?tab=accounts',_=>i.replace(/unt-[^]+?\n *([\w&].+)[^]+?r".+?([\d,]+)/g,(_,s,r)=>a.push([s,r=r.split`,`.join``,t-=r,n--]))&&C(a.map(([s,r])=>s.replace(/&#?(.+?);/g,(_,m)=>String.fromCharCode('0'+m|0||38))+` ${r/t*n}x`).join`
`)))))
Function which accepts a the ID as a string and returns the result to a callback.
I tried to make it robust and it should work for any profile as of time of submission with one possible exception: the accounts tab on the network profile contains a profile summary which can be edited by the user so it's possible for a user to write something to fool the regex in their bio.
Explanation
(i,C)=>(p='https://codegolf.stackexchange.com',
// n = count of sites, t = total sum of reputation, a = array of (siteName, rep) tuples
n=t=a=[],
// Function which performs a GET request.
// The body is appended to `i` and the callback is called with the location header.
g=(u,c)=>require('https').get(u,r=>r.on('data',d=>i+=d,r.on('end',_=>c(r.headers.location))))
// Load the user's PPCG profile.
)(p+'/users/'+i,l=>
// Since we only provided the ID and not the name in the URL, it will return a 301 redirect.
// Load the redirect.
g(p+l,_=>
// The first URL on the body to match this regex is the network profile URL of the user.
// Note that this comes before the custom content in the profile
// (which could contain other network profile URLs).
// This request will result in a 301 if the user's name is different on the
// network profile than PPCG, so the first character plus 0 will force a redirect
// because display names are a minimum of 3 characters long.
g(s=i.match(/ht.+\/s[^"]+\d\/[^?]/)+0,l=>
// Load the accounts tab of the network profile.
g(new URL(l,s)+'?tab=accounts',_=>
// Find each site and reputation count.
i.replace(/unt-[^]+?\n *([\w&].+)[^]+?r".+?([\d,]+)/g,
// Remove the commas from the rep value, populate `a` and add them up for the average.
// decodeURIComponent is annoyingly long but there's no shorter way to correctly display
// sites like the Japanese site which is made solely of URI encoded characters.
(_,s,r)=>a.push([s,r=r.split`,`.join``,t-=r,n--]))&&
// Calculate and print the results.
// There is a lot of bytes dedicated here to unescaping HTML entities because
// unfortunately there's no easy way to correctly display sites like the Japanese site
// which is made solely of HTML encoded characters.
// As far as I can tell the only escapes used in site names are the `&#xXX` style
// and `&`.
C(a.map(([s,r])=>s.replace(/&#?(.+?);/g,(_,m)=>String.fromCharCode(0+m|0||38))+` ${r/t*n}x`).join`
`)))))