Explorar o código

Add stat display with z-scores

Cadel Watson hai 1 ano
pai
achega
f05b55cf69
Modificáronse 5 ficheiros con 189 adicións e 40 borrados
  1. 38 0
      src/Card.elm
  2. 6 1
      src/Database.elm
  3. 80 25
      src/Main.elm
  4. 44 0
      src/Stats.elm
  5. 21 14
      tailwind.config.js

+ 38 - 0
src/Card.elm

@@ -2,12 +2,14 @@ module Card exposing
     ( CardData
     , CardDetails
     , CardPerformanceData
+    , CardPerformanceDistributions
     , CardType(..)
     , ManaColor(..)
     , ManaCost(..)
     , Power(..)
     , alpa
     , alsa
+    , calculatePerformanceDistributions
     , gihwr
     , iwd
     , manaCostToSymbol
@@ -16,6 +18,7 @@ module Card exposing
     )
 
 import Parser as P exposing ((|.), Parser, Trailing(..))
+import Stats exposing (NormalDistribution)
 
 
 type ManaColor
@@ -102,6 +105,41 @@ iwd card =
     card.performance.improvementWhenDrawn
 
 
+type alias CardPerformanceDistributions =
+    { alsa : NormalDistribution
+    , alpa : NormalDistribution
+    , gihwr : NormalDistribution
+    , pickRate : NormalDistribution
+    , iwd : NormalDistribution
+    }
+
+
+calculatePerformanceDistributions : List CardData -> CardPerformanceDistributions
+calculatePerformanceDistributions cards =
+    let
+        pickRates =
+            List.map pickRate cards
+
+        alsaValues =
+            List.filterMap alsa cards
+
+        alpaValues =
+            List.filterMap alpa cards
+
+        gihwrValues =
+            List.filterMap gihwr cards
+
+        iwdValues =
+            List.filterMap iwd cards
+    in
+    { alsa = Stats.getNormalDistribution alsaValues
+    , alpa = Stats.getNormalDistribution alpaValues
+    , gihwr = Stats.getNormalDistribution gihwrValues
+    , pickRate = Stats.getNormalDistribution pickRates
+    , iwd = Stats.getNormalDistribution iwdValues
+    }
+
+
 type ManaCost
     = X
     | Y

+ 6 - 1
src/Database.elm

@@ -1,4 +1,4 @@
-module Database exposing (Database, decode, get)
+module Database exposing (Database, decode, get, getAll)
 
 import Card exposing (CardData, CardDetails, CardPerformanceData, CardType(..), ManaColor(..), Power(..), parseManaCost)
 import Dict exposing (Dict)
@@ -16,6 +16,11 @@ get name (Database db) =
     Dict.get name db
 
 
+getAll : Database -> List CardData
+getAll (Database db) =
+    Dict.values db
+
+
 decode : String -> String -> Result String Database
 decode setData draftData =
     case ( decodeString decodeSetData setData, decodeString decodePerformanceData draftData ) of

+ 80 - 25
src/Main.elm

@@ -1,7 +1,7 @@
 module Main exposing (..)
 
 import Browser exposing (Document)
-import Card exposing (CardData, manaCostToSymbol)
+import Card exposing (CardData, CardPerformanceDistributions, calculatePerformanceDistributions, manaCostToSymbol)
 import Chart as C
 import Chart.Attributes as CA
 import Database
@@ -15,6 +15,7 @@ import Icon exposing (chevronDown, chevronUp)
 import List.Extra as List
 import Round
 import Signals
+import Stats exposing (zscore)
 import Tuple exposing (first, second)
 import Zipper
 
@@ -53,6 +54,7 @@ type alias ToolboxAccordion =
 type alias ReadyModel =
     { draft : Draft
     , database : Database.Database
+    , performanceDistributions : CardPerformanceDistributions
     , highlighted : Maybe CardData
     , flipHighlighted : Bool
     , focusStat : FocusStat
@@ -74,6 +76,7 @@ init flags =
             ( Ready
                 { draft = draftData
                 , database = database
+                , performanceDistributions = calculatePerformanceDistributions (Database.getAll database)
                 , highlighted = Nothing
                 , flipHighlighted = False
                 , focusStat = FocusPickRate
@@ -542,7 +545,7 @@ viewHighlightedCard model =
                         , onClick FlipHighlightedCard
                         ]
                         []
-                    , viewCardDetailedPerformance highlighted
+                    , viewCardDetailedPerformance model.performanceDistributions highlighted
                     ]
 
             Nothing ->
@@ -555,40 +558,92 @@ formatPercentage value =
     Round.round 2 (value * 100) ++ "%"
 
 
-viewCardDetailedPerformance : CardData -> Html Msg
-viewCardDetailedPerformance card =
+viewCardDetailedPerformance : CardPerformanceDistributions -> CardData -> Html Msg
+viewCardDetailedPerformance distributions card =
     let
-        makeCardFacts : CardData -> List ( String, String )
+        makeCardFacts : CardData -> List ( String, String, Float )
         makeCardFacts cardData =
-            [ ( "Pick rate"
-              , Just <|
-                    formatPercentage (Card.pickRate cardData)
-              )
-            , ( "GIHWR"
-              , Maybe.map formatPercentage (Card.gihwr cardData)
-              )
-            , ( "ALPA"
-              , Maybe.map (Round.round 2) (Card.alpa cardData)
-              )
-            , ( "ALSA"
-              , Maybe.map (Round.round 2) (Card.alsa cardData)
-              )
+            [ Just
+                ( "Pick rate"
+                , formatPercentage (Card.pickRate cardData)
+                , zscore (Card.pickRate cardData) distributions.pickRate
+                )
+            , Maybe.map
+                (\gihwr ->
+                    ( "GIHWR"
+                    , formatPercentage gihwr
+                    , zscore gihwr distributions.gihwr
+                    )
+                )
+                (Card.gihwr cardData)
+            , Maybe.map
+                (\alpa ->
+                    ( "ALPA"
+                    , Round.round 2 alpa
+                    , negate (zscore alpa distributions.alpa)
+                    )
+                )
+                (Card.alpa cardData)
+            , Maybe.map
+                (\alsa ->
+                    ( "ALSA"
+                    , Round.round 2 alsa
+                    , negate (zscore alsa distributions.alsa)
+                    )
+                )
+                (Card.alsa cardData)
+            , Maybe.map
+                (\iwd ->
+                    ( "IWD"
+                    , Round.round 2 iwd
+                    , zscore iwd distributions.iwd
+                    )
+                )
+                (Card.iwd cardData)
             ]
-                |> List.filterMap (\( label, value ) -> Maybe.map (\v -> ( label, v )) value)
+                |> List.filterMap identity
 
-        viewFact : ( String, String ) -> Html Msg
-        viewFact ( label, value ) =
+        viewFact : ( String, String, Float ) -> Html Msg
+        viewFact ( label, value, zscore ) =
             div
-                [ class "" ]
-                [ div [] [ text label ]
-                , div [] [ text value ]
+                [ classList
+                    [ ( "rounded", True )
+                    ]
+                ]
+                [ div [ class "text-white" ] [ text label ]
+                , div
+                    [ classList
+                        [ ( "bg-" ++ characteriseZScore zscore ++ "-500", True )
+                        , ( "rounded text-xl font-medium p-2", True )
+                        ]
+                    ]
+                    [ text value ]
                 ]
     in
     div
-        [ class "" ]
+        [ class "grid grid-cols-2 gap-4 rounded mt-4" ]
         (List.map viewFact (makeCardFacts card))
 
 
+characteriseZScore : Float -> String
+characteriseZScore x =
+    -- assume up is good
+    if x < -1 then
+        "red"
+
+    else if x < -0.5 then
+        "yellow"
+
+    else if x > 1 then
+        "purple"
+
+    else if x > 0.5 then
+        "green"
+
+    else
+        "gray"
+
+
 viewKeyedCard : ReadyModel -> Bool -> Draft.PickCard -> ( String, Html Msg )
 viewKeyedCard model wasChosen { name, frontImage, backImage } =
     let

+ 44 - 0
src/Stats.elm

@@ -0,0 +1,44 @@
+module Stats exposing
+    ( NormalDistribution
+    , getNormalDistribution
+    , mean
+    , stdDev
+    , zscore
+    )
+
+
+type NormalDistribution
+    = NormalDistribution Float Float -- mean, std dev.
+
+
+getNormalDistribution : List Float -> NormalDistribution
+getNormalDistribution xs =
+    let
+        n =
+            toFloat (List.length xs)
+
+        mu =
+            List.sum xs / n
+
+        variance =
+            List.sum (List.map (\x -> (x - mu) ^ 2) xs) / n
+
+        sigma =
+            sqrt variance
+    in
+    NormalDistribution mu sigma
+
+
+mean : NormalDistribution -> Float
+mean (NormalDistribution m _) =
+    m
+
+
+stdDev : NormalDistribution -> Float
+stdDev (NormalDistribution _ s) =
+    s
+
+
+zscore : Float -> NormalDistribution -> Float
+zscore x (NormalDistribution m s) =
+    (x - m) / s

+ 21 - 14
tailwind.config.js

@@ -1,16 +1,23 @@
 module.exports = {
-  purge: [
-    './src/**/*.elm',
-    './js/app.js',
-    './html/index.html',
-    './css/styles.css',
-  ],
-  darkMode: false, // or 'media' or 'class'
-  theme: {
-    extend: {},
-  },
-  variants: {
-    extend: {},
-  },
-  plugins: [],
+    purge: [
+        './src/**/*.elm',
+        './js/app.js',
+        './html/index.html',
+        './css/styles.css',
+    ],
+    darkMode: false, // or 'media' or 'class'
+    theme: {
+        extend: {},
+    },
+    variants: {
+        extend: {},
+    },
+    plugins: [],
+    safelist: [
+        'bg-gray-500',
+        'bg-yellow-500',
+        'bg-red-500',
+        'bg-green-500',
+        'bg-purple-500',
+    ]
 };