This workbook can help rank GPUs according a mixture of features (with the weights determined by the user) and graph it against price.
Firstly, pull in the parameters from Wikipedia (NVidia cards, AMD cards) for the cards under consideration (more can easily be added, though, to keep the list reasonable, don't add cards with >1000 single precision GFLOPs), each with one example of the product on Amazon (more Amazon examples can be added below) :
In [22]:
raw="""
name | sh:tx:rop | mem | bw|bus|ocl|single|double|watts| amz:brand:comment
GeForce GT740 DDR3 4G| 384:32:16 | 4096| 28|128|1.2| 763 | 0 | 65 | B00KJGYOBQ
GeForce GTX 750 1Gb | 512:32:16 | 1024| 80|128|1.2| 1044 | 32.6 | 55 | B00IDG3NDY
GeForce GTX 750 2Gb | 512:32:16 | 2048| 80|128|1.2| 1044 | 32.6 | 55 | B00J3ZNB04
GeForce GTX 750Ti 2Gb| 640:40:16 | 2048| 80|128|1.2| 1306 | 40.8 | 60 | B00IDG3IDO
GeForce GTX 750Ti 4Gb| 640:40:16 | 4096| 80|128|1.2| 1306 | 40.8 | 60 | B00T4RJ8FI
GeForce GTX 760 2Gb |1152:96:32 | 2048|192|256|1.2| 2257 | 94 | 170 | B00DT5R3EO
GeForce GTX 760 4Gb |1152:96:32 | 4096|192|256|1.2| 2257 | 94 | 170 | B00E9O28DU
GeForce GTX 960 2Gb |1024:64:32 | 2048|112|128|1.2| 2308 | 72.1 | 120 | B00SC6HAS4
GeForce GTX 960 4Gb |1024:64:32 | 4096|112|128|1.2| 2308 | 72.1 | 120 | B00UOYQ5LA
GeForce GTX 970 |1664:104:56| 3584|196|224|1.2| 3494 | 109 | 145 | B00NVODXR4
GeForce GTX 980 |2048:128:64| 4096|224|256|1.2| 4612 | 144 | 165 | B00NT9UT3M
GeForce GTX 980 Ti |2816:176:96| 6144|336|384|1.2| 5632 | 176 | 250 | B00YNEIAWY
GeForce GTX Titan X |3072:192:96|12288|336|384|1.2| 6144 | 192 | 250 | B00UXTN5P0
HD 5570 1Gb | 400:20:8 | 1024| 29|128|1.2| 520 | 0 | 39 | B004JU260O
R9 280 |1792:112:32| 3072|240|384|1.2| 2964 | 741 | 250 | B00IZXOW80
R9 290 |2560:160:64| 4096|320|512|2.0| 4848 | 606 | 275 | B00V4JVY1A
R9 290X |2816:176:64| 4096|320|512|2.0| 5632 | 704 | 290 | B00FLMKQY2
R9 380 2Gb |1792:112:32| 2048|182|256|2.1| 3476 | 217 | 190 | B00ZGL8EBK
R9 380 4Gb |1792:112:32| 4096|182|256|2.1| 3476 | 217 | 190 | B00ZGF3TUC
R9 390 8Gb |2560:160:64| 8192|384|512|2.1| 5120 | 640 | 275 | B00ZGL8CYY
R9 390X |2816:176:64| 8192|384|512|2.1| 5914 | 739 | 275 | B00ZGL8CFI
"""
import re
arr = [ re.split(r'\s*[|:]\s*',l) for l in raw.split('\n') if len(l)>0]
headings = arr[0]
cards=[ { h:(e if h in 'name.amz' else float(e)) for h,e in zip(headings,a) } for a in arr[1:] ]
pricing={ a['amz']:{k:v for k,v in a.items() if k in 'name:brand:comment:amz'} for a in cards}
#for c in cards:print("%s|%s" % (c['name'], c['amz']))
Now the GPU card data is in a nice array of dictionary entries, with numeric entries for all but 'name' and the Amazon item ID, indexed in the same order as 'raw'.
Here, one can put additional Amazon product codes that refer to the same card from a Compute perspective (different manufacturer and/or different ports may make the cards different from a gaming user's perspective, of course).
TODO : Add in more prices, to get a broader sample
In [23]:
raw="""
name |amz:brand:comment
GeForce GTX 750 1Gb |
GeForce GTX 750 2Gb |
GeForce GTX 750Ti 2Gb|
GeForce GTX 750Ti 4Gb|
GeForce GTX 760 2Gb |
GeForce GTX 760 4Gb |
GeForce GTX 960 2Gb |
GeForce GTX 960 4Gb |
GeForce GTX 970 |B00OQUMGM0:GigabyteMiniITX
GeForce GTX 970 |B00NH5ZNWA:PNY
GeForce GTX 980 |
GeForce GTX 980 Ti |B00YDAYOK0:EVGA
GeForce GTX Titan X |
R9 290 |
R9 290X |
R9 380 2Gb |
R9 380 4Gb |
R9 390 8Gb |B00ZQ9JKSS:Visiontech
R9 390 8Gb |B00ZQ3QVU4:Asus
R9 390 8Gb |B00ZGF3UAQ:Gigabyte
R9 390 8Gb |B00ZGL8CYY:Sapphire
R9 390 8Gb |B00ZGF0UAE:MSI
R9 390X |B00ZGF3TNO:Gigabyte
R9 390X |B00ZGL8CFI:Sapphire
R9 390X |B00ZGF158A:MSI
"""
arr = [ re.split(r'\s*[|:]\s*',l) for l in raw.split('\n') if len(l)>0]
headings = arr[0]
equivs =[ { h:e for h,e in zip(headings,a) } for a in arr[1:] ]
pricing.update({ a['amz']:a for a in equivs if a['amz'] })
#pricing
In [24]:
cache={'B00IDG3IDO': 139.99, 'B00OQUMGM0': 299.99, 'B00T4RJ8FI': 349.99, 'B00ZGL8CYY': 359.42, 'B00NT9UT3M': 507.82, 'B00ZGF0UAE': 369.99, 'B00YNEIAWY': 698.85, 'B00ZGF158A': 429.99, 'B00IZXOW80': 249.99, 'B00FLMKQY2': 339.99, 'B00ZGF3UAQ': 329.99, 'B00ZGL8EBK': 216.53, 'B00IDG3NDY': 114.12, 'B00V4JVY1A': 333.26, 'B00ZGL8CFI': 458.63, 'B00UOYQ5LA': 239.99, 'B00YDAYOK0': 679.99, 'B00UXTN5P0': 1029.99, 'B00ZQ3QVU4': 349.99, 'B00ZQ9JKSS': 368.63, 'B00DT5R3EO': 199.99, 'B00NVODXR4': 337.99, 'B00KJGYOBQ': 99.99, 'B004JU260O': 180.99, 'B00J3ZNB04': 149.37, 'B00ZGF3TUC': 229.99, 'B00SC6HAS4': 199.99, 'B00E9O28DU': 274.99}
for k,v in cache.items():
if k in pricing and pricing[k].get('px',None) is None:
pricing[k]['px'] = v
#pricing
Rather than use their API (which creates the issue of putting the keys into GitHub), just grab the pages. NB: The page caches the prices found into the data structure to avoid doing this too often!
The price downloading/parsing requires that you have requests
and BeautifulSoup
installed : pip install requests BeautifulSoup4
In [25]:
import requests
from bs4 import BeautifulSoup
BASE_URL = "http://www.amazon.com/exec/obidos/ASIN/"
for k,v in pricing.items():
if v.get('px', None) is None:
name = v.get('name', 'UNKNOWN')
print("Fetching price for %s from Amazon.com" % (name))
r = requests.get(BASE_URL + k)
soup = BeautifulSoup(r.text, 'html.parser')
price = None
try:
ele = soup.find(id="priceblock_ourprice")
price = float(ele.text.replace('$','').replace(',',''))
except AttributeError:
print("Didn't find the 'price' element for %s (%s)" % (name, k))
v['px']=price
print("Finished downloading prices : Run the 'cache' script below to save the data")
In [26]:
print({ k:v['px'] for k,v in pricing.items() if v.get('px',None) is not None})
In [27]:
for c in cards:
pxs = [ v['px'] for k,v in pricing.items() if v['name']== c['name'] and v.get('px', None) is not None ]
c['px']=min(pxs)
c['px_max']=max(pxs)
In [28]:
for c in cards:
if c.get('px', None) is not None:
print("%s| $%7.2f" % ((c['name']+' '*30)[:24], c['px']))
The concept here is that one can focus on a 'basecard' (for instance, one you already have, or one you've looked at closely), and then assign multiplicative weights to each of a GPU card's qualities, and come up with a 'relative performance' according to that weighting scheme.
In [29]:
basecard = 'GeForce GTX 760 2Gb' # Name should match a card with full data above
basedata = [ c for c in cards if c['name']==basecard ][0]
multipliers = dict(single=2., mem=1.) # FLOPs are twice as important as memory, all else ignored
cards_filtered = [c for c in cards if c['ocl']<3. and c['px']<500. ]
def evaluate_card(base, d, mult):
comp=0.
for (k,v) in mult.items():
if d.get(k,None) is not None and base.get(k,None) is not None:
comp += v*d[k]/base[k]
return comp
x=[ c.get('px',None) for c in cards_filtered ]
y=[ evaluate_card(basedata, c, multipliers) for c in cards_filtered ]
l=[ c['name'] for c in cards_filtered ]
for name,score,px in sorted(zip(l,y,x), key=lambda p: -p[1]):
print("%s| $%7.2f | %5.2f" % ((name+' '*30)[:24], px, score))
In [30]:
%matplotlib inline
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 8))
plt.plot(x,y, 'ro')
for i, xy in enumerate(zip(x, y)):
plt.annotate('%s' % (l[i]), xy=xy, xytext=(5,.05), textcoords='offset points')
plt.show()
In [ ]: