소스 검색

Add stat display with z-scores

Cadel Watson 1 년 전
부모
커밋
f05b55cf69
5개의 변경된 파일189개의 추가작업 그리고 40개의 파일을 삭제
  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
     ( CardData
     , CardDetails
     , CardDetails
     , CardPerformanceData
     , CardPerformanceData
+    , CardPerformanceDistributions
     , CardType(..)
     , CardType(..)
     , ManaColor(..)
     , ManaColor(..)
     , ManaCost(..)
     , ManaCost(..)
     , Power(..)
     , Power(..)
     , alpa
     , alpa
     , alsa
     , alsa
+    , calculatePerformanceDistributions
     , gihwr
     , gihwr
     , iwd
     , iwd
     , manaCostToSymbol
     , manaCostToSymbol
@@ -16,6 +18,7 @@ module Card exposing
     )
     )
 
 
 import Parser as P exposing ((|.), Parser, Trailing(..))
 import Parser as P exposing ((|.), Parser, Trailing(..))
+import Stats exposing (NormalDistribution)
 
 
 
 
 type ManaColor
 type ManaColor
@@ -102,6 +105,41 @@ iwd card =
     card.performance.improvementWhenDrawn
     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
 type ManaCost
     = X
     = X
     | Y
     | 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 Card exposing (CardData, CardDetails, CardPerformanceData, CardType(..), ManaColor(..), Power(..), parseManaCost)
 import Dict exposing (Dict)
 import Dict exposing (Dict)
@@ -16,6 +16,11 @@ get name (Database db) =
     Dict.get name db
     Dict.get name db
 
 
 
 
+getAll : Database -> List CardData
+getAll (Database db) =
+    Dict.values db
+
+
 decode : String -> String -> Result String Database
 decode : String -> String -> Result String Database
 decode setData draftData =
 decode setData draftData =
     case ( decodeString decodeSetData setData, decodeString decodePerformanceData draftData ) of
     case ( decodeString decodeSetData setData, decodeString decodePerformanceData draftData ) of

+ 80 - 25
src/Main.elm

@@ -1,7 +1,7 @@
 module Main exposing (..)
 module Main exposing (..)
 
 
 import Browser exposing (Document)
 import Browser exposing (Document)
-import Card exposing (CardData, manaCostToSymbol)
+import Card exposing (CardData, CardPerformanceDistributions, calculatePerformanceDistributions, manaCostToSymbol)
 import Chart as C
 import Chart as C
 import Chart.Attributes as CA
 import Chart.Attributes as CA
 import Database
 import Database
@@ -15,6 +15,7 @@ import Icon exposing (chevronDown, chevronUp)
 import List.Extra as List
 import List.Extra as List
 import Round
 import Round
 import Signals
 import Signals
+import Stats exposing (zscore)
 import Tuple exposing (first, second)
 import Tuple exposing (first, second)
 import Zipper
 import Zipper
 
 
@@ -53,6 +54,7 @@ type alias ToolboxAccordion =
 type alias ReadyModel =
 type alias ReadyModel =
     { draft : Draft
     { draft : Draft
     , database : Database.Database
     , database : Database.Database
+    , performanceDistributions : CardPerformanceDistributions
     , highlighted : Maybe CardData
     , highlighted : Maybe CardData
     , flipHighlighted : Bool
     , flipHighlighted : Bool
     , focusStat : FocusStat
     , focusStat : FocusStat
@@ -74,6 +76,7 @@ init flags =
             ( Ready
             ( Ready
                 { draft = draftData
                 { draft = draftData
                 , database = database
                 , database = database
+                , performanceDistributions = calculatePerformanceDistributions (Database.getAll database)
                 , highlighted = Nothing
                 , highlighted = Nothing
                 , flipHighlighted = False
                 , flipHighlighted = False
                 , focusStat = FocusPickRate
                 , focusStat = FocusPickRate
@@ -542,7 +545,7 @@ viewHighlightedCard model =
                         , onClick FlipHighlightedCard
                         , onClick FlipHighlightedCard
                         ]
                         ]
                         []
                         []
-                    , viewCardDetailedPerformance highlighted
+                    , viewCardDetailedPerformance model.performanceDistributions highlighted
                     ]
                     ]
 
 
             Nothing ->
             Nothing ->
@@ -555,40 +558,92 @@ formatPercentage value =
     Round.round 2 (value * 100) ++ "%"
     Round.round 2 (value * 100) ++ "%"
 
 
 
 
-viewCardDetailedPerformance : CardData -> Html Msg
-viewCardDetailedPerformance card =
+viewCardDetailedPerformance : CardPerformanceDistributions -> CardData -> Html Msg
+viewCardDetailedPerformance distributions card =
     let
     let
-        makeCardFacts : CardData -> List ( String, String )
+        makeCardFacts : CardData -> List ( String, String, Float )
         makeCardFacts cardData =
         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
             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
     in
     div
     div
-        [ class "" ]
+        [ class "grid grid-cols-2 gap-4 rounded mt-4" ]
         (List.map viewFact (makeCardFacts card))
         (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 : ReadyModel -> Bool -> Draft.PickCard -> ( String, Html Msg )
 viewKeyedCard model wasChosen { name, frontImage, backImage } =
 viewKeyedCard model wasChosen { name, frontImage, backImage } =
     let
     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 = {
 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',
+    ]
 };
 };