Chaining list operations in Python
This is more &quot;streaming&quot; in a sense:
from heapq import nlargest
from operator import itemgetter

def top_ten_scorers(stats):
    players = (dict(name=name, ppg=points/games, team=team)
               for team, players in stats.items()
               for name, games, points in players
               if games &gt;= 15)
    return nlargest(10, players, key=itemgetter('ppg'))

Your with_teams and with_ppg are fully computed lists, and then sorted creates another one that it then sorts, and then you throw away all but ten elements of it.
My players is a generator iterator, computing more elements on the fly as requested. The players = ... assignment only sets up the iterator, but nothing gets processed yet.
Then nlargest consumes players one by one, keeping only the top 10 seen so far and returning them sorted (in descending order). Could also be more efficient than sorting everything, depending on the number of eligible players.
I actually found your first two steps more confusing than helpful, as your with_teams creates an intermediate result/format to understand. I think it's simpler and easier to read to just directly produce the player dicts from the stats. Then again, I might be biased towards this and away from yours because I'm used to Python, which, as you say, isn't much into chaining.
Btw, here's an old message from Guido about some forms of chaining. Not sure it relates to what we have here, but perhaps interesting anyway.
I used dict(...) just for brevity, but {...} is faster, so you might want to keep the latter:
Setup:
name, ppg, team = 'Betnijah Laney', 263/16, 'ATL'

Round 1:
  347.041 ns  dict(name=name, ppg=ppg, team=team)
  128.325 ns  {'name': name, 'ppg': ppg, 'team': team}

Round 2:
  350.576 ns  dict(name=name, ppg=ppg, team=team)
  129.106 ns  {'name': name, 'ppg': ppg, 'team': team}

Round 3:
  347.753 ns  dict(name=name, ppg=ppg, team=team)
  130.734 ns  {'name': name, 'ppg': ppg, 'team': team}