{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} {-| Module : Graphics.Vega.VegaLite.Transform Copyright : (c) Douglas Burke, 2018-2020 License : BSD3 Maintainer : dburke.gw@gmail.com Stability : unstable Portability : CPP, OverloadedStrings Types related to data transformation. Note that this does not include some of the \"obvious\" types, in particular @Operation@ and @Filter@ as these types are inter-related and end up requiring a number of other types unrelated to transformations. -} module Graphics.Vega.VegaLite.Transform ( Operation(..) , Window(..) , WOperation(..) , BinProperty(..) , WindowProperty(..) , ImputeProperty(..) , ImMethod(..) -- not for external export , aggregate_ , op_ , binned_ , impute_ , bin , binProperty , operationSpec , windowTS , joinAggregateTS , imputeTS ) where import qualified Data.Aeson as A import qualified Data.Text as T import Data.Aeson ((.=), object, toJSON) import Data.Maybe (mapMaybe) #if !(MIN_VERSION_base(4, 12, 0)) import Data.Monoid ((<>)) #endif import Graphics.Vega.VegaLite.Data ( DataValue , DataValues , dataValueSpec , dataValuesSpecs ) import Graphics.Vega.VegaLite.Foundation ( FieldName , SortField , sortFieldSpec , field_ , fromT , allowNull ) import Graphics.Vega.VegaLite.Specification ( VLSpec , LabelledSpec , TransformSpec(..) , SelectionLabel ) {-| Type of aggregation operation. See the <https://vega.github.io/vega-lite/docs/aggregate.html#ops Vega-Lite documentation> for more details. The @Average@ constructor was removed in version @0.4.0.0@; use 'Mean' instead. -} data Operation = ArgMax (Maybe FieldName) -- ^ An input data object containing the maximum field value to be used -- in an aggregation operation. -- -- If supplied as part of an encoding aggregation, the parameter -- should be 'Just' the name of the field to maximise. When used -- as part of a transform the parameter should be 'Nothing' as the -- field is specified in the 'Graphics.Vega.VegaLite.aggregate' call. -- -- Encoding example, to find the production budget for the maximum -- US grossing film in each genre: -- -- @ -- 'Graphics.Vega.VegaLite.encoding' -- . 'Graphics.Vega.VegaLite.position' 'Graphics.Vega.VegaLite.X' -- [ 'Graphics.Vega.VegaLite.PName' \"Production_Budget\" -- , 'Graphics.Vega.VegaLite.PmType' 'Graphics.Vega.VegaLite.Quantitative' -- , 'Graphics.Vega.VegaLite.PAggregate' ('ArgMax' ('Just' \"US_Gross\")) -- ] -- . 'Graphics.Vega.VegaLite.position' 'Graphics.Vega.VegaLite.Y' ['Graphics.Vega.VegaLite.PName' \"Major_Genre\", 'Graphics.Vega.VegaLite.PmType' 'Graphics.Vega.VegaLite.Nominal'] -- @ -- -- An example of its use as part of an 'Graphics.Vega.VegaLite.aggregate' call: -- -- @ -- 'Graphics.Vega.VegaLite.transform' -- . 'Graphics.Vega.VegaLite.aggregate' -- [ 'Graphics.Vega.VegaLite.opAs' ('ArgMax' 'Nothing') \"US_Gross\" \"amUSGross\"] -- [\"Major_Genre\"] -- @ -- -- The optional field name was added in the @0.4.0.0@ release. | ArgMin (Maybe FieldName) -- ^ An input data object containing the minimum field value to be used -- in an aggregation operation. See 'ArgMax' for a discussion of the -- optional argument. -- -- The optional field name was added in the @0.4.0.0@ release. | CI0 -- ^ Lower 95% confidence interval to be used in an aggregation operation. | CI1 -- ^ Upper 95% confidence interval to be used in an aggregation operation. | Count -- ^ Total count of data objects to be used in an aggregation operation. | Distinct -- ^ Count of distinct data objects to be used in an aggregation operation. | Max -- ^ Maximum field value to be used in an aggregation operation. | Mean -- ^ Mean field value to be used in an aggregation operation. | Median -- ^ Median field value to be used in an aggregation operation. | Min -- ^ Minimum field value to be used in an aggregation operation. | Missing -- ^ Count of @null@ or @undefined@ field value to be used in an aggregation operation. | Product -- ^ Product of field values to be used in an aggregate operation. -- -- This was added in Vega-Lite 4.6.0. -- -- @since 0.7.0.0 | Q1 -- ^ Lower quartile boundary of field values to be used in an aggregation operation. | Q3 -- ^ Upper quartile boundary of field values to be used in an aggregation operation. | Stderr -- ^ Standard error of field values to be used in an aggregate operation. | Stdev -- ^ Sample standard deviation of field values to be used in an aggregate operation. | StdevP -- ^ Population standard deviation of field values to be used in an aggregate operation. | Sum -- ^ Sum of field values to be used in an aggregate operation. | Valid -- ^ Count of values that are not @null@, @undefined@, or @NaN@ to be used in an -- aggregation operation. | Variance -- ^ Sample variance of field values to be used in an aggregate operation. | VarianceP -- ^ Population variance of field values to be used in an aggregate operation. -- Unlike Elm, not checking if the string is empty for ArgMin/Max operationSpec :: Operation -> VLSpec operationSpec (ArgMax Nothing) = "argmax" operationSpec (ArgMax (Just s)) = object ["argmax" .= s] operationSpec (ArgMin Nothing) = "argmin" operationSpec (ArgMin (Just s)) = object ["argmin" .= s] operationSpec CI0 = "ci0" operationSpec CI1 = "ci1" operationSpec Count = "count" operationSpec Distinct = "distinct" operationSpec Max = "max" operationSpec Mean = "mean" operationSpec Median = "median" operationSpec Min = "min" operationSpec Missing = "missing" operationSpec Product = "product" operationSpec Q1 = "q1" operationSpec Q3 = "q3" operationSpec Stderr = "stderr" operationSpec Stdev = "stdev" operationSpec StdevP = "stdevp" operationSpec Sum = "sum" operationSpec Valid = "valid" operationSpec Variance = "variance" operationSpec VarianceP = "variancep" aggregate_ :: Operation -> LabelledSpec aggregate_ op = "aggregate" .= operationSpec op op_ :: Operation -> LabelledSpec op_ op = "op" .= operationSpec op -- | Window transformations. -- -- @since 0.4.0.0 data Window = WAggregateOp Operation -- ^ An aggregrate operation to be used in a window transformation. | WOp WOperation -- ^ Window-specific operation to be used in a window transformation. | WParam Int -- ^ Numeric parameter for window-only operations that can be parameterised -- ('Ntile', 'Lag', 'Lead' and 'NthValue'). | WField FieldName -- ^ Field for which to compute a window operation. Not needed for operations -- that do not apply to fields such as 'Count', 'Rank', and 'DenseRank'. windowFieldProperty :: Window -> LabelledSpec windowFieldProperty (WAggregateOp op) = "op" .= operationSpec op windowFieldProperty (WOp op) = "op" .= wOperationLabel op windowFieldProperty (WParam n) = "param" .= n windowFieldProperty (WField f) = field_ f -- | Window-specific operation for transformations (for use with 'WOp'). -- -- @since 0.4.0.0 data WOperation = RowNumber -- ^ Assign consecutive row number to values in a data object to be applied in a window transform. | Rank -- ^ Rank function to be applied in a window transform. | DenseRank -- ^ Dense rank function to be applied in a window transform. | PercentRank -- ^ Percentile of values in a sliding window to be applied in a window transform. | CumeDist -- ^ Cumulative distribution function to be applied in a window transform. | Ntile -- ^ Value preceding the current object in a sliding window to be applied in a window transform. | Lag -- ^ Value preceding the current object in a sliding window to be applied in a window transform. | Lead -- ^ Value following the current object in a sliding window to be applied in a window transform. | FirstValue -- ^ First value in a sliding window to be applied in a window transform. | LastValue -- ^ Last value in a sliding window to be applied in a window transform. | NthValue -- ^ Nth value in a sliding window to be applied in a window transform. wOperationLabel :: WOperation -> T.Text wOperationLabel RowNumber = "row_number" wOperationLabel Rank = "rank" wOperationLabel DenseRank = "dense_rank" wOperationLabel PercentRank = "percent_rank" wOperationLabel CumeDist = "cume_dist" wOperationLabel Ntile = "ntile" wOperationLabel Lag = "lag" wOperationLabel Lead = "lead" wOperationLabel FirstValue = "first_value" wOperationLabel LastValue = "last_value" wOperationLabel NthValue = "nth_value" {-| Type of binning property to customise. See the <https://vega.github.io/vega-lite/docs/bin.html Vega-Lite documentation> for more details. This is used with: 'Graphics.Vega.VegaLite.binAs', 'Graphics.Vega.VegaLite.DBin', 'Graphics.Vega.VegaLite.FBin', 'Graphics.Vega.VegaLite.HBin', 'Graphics.Vega.VegaLite.MBin', 'Graphics.Vega.VegaLite.OBin', 'Graphics.Vega.VegaLite.PBin', and 'Graphics.Vega.VegaLite.TBin'. -} -- based on schema 3.3.0 #/definitions/BinParams data BinProperty = AlreadyBinned Bool -- ^ Should the input data be treated as already binned? -- -- @since 0.4.0.0 | BinAnchor Double -- ^ A value in the binned domain at which to anchor the bins, shifting the bin -- boundaries if necessary to ensure that a boundary aligns with the anchor -- value. -- -- @since 0.4.0.0 | Base Double -- ^ The number base to use for automatic bin determination. -- -- Default is @10@. | Divide [Double] -- ^ Scale factors indicating allowable subdivisions. -- -- Default is @[5, 2]@. -- -- Prior to @0.4.0.0@ the @Divide@ constructor took two numbers. | Extent Double Double -- ^ The range (minimum, maximum) of the desired bin values. | MaxBins Int -- ^ The maxium number of bins. -- -- Default is @6@ for 'Graphics.Vega.VegaLite.row', 'Graphics.Vega.VegaLite.column', and 'Graphics.Vega.VegaLite.shape' channels, -- @10@ otherwise. | MinStep Double -- ^ A minimum allowable step size. | Nice Bool -- ^ If @True@, the bin boundaries are adjusted to use human-friendly values, -- such as multiples of ten. -- -- Default is @True@. | SelectionExtent SelectionLabel -- ^ Set the range based on an interactive selection. The label -- must reference an interval selection, but this constraint is -- /not enforced/ at compile or run time. -- -- @ -- sel = 'Graphics.Vega.VegaLite.selection' -- . 'Graphics.Vega.VegaLite.select' \"brush\" 'Graphics.Vega.VegaLite.Interval' [ 'Graphics.Vega.VegaLite.Encodings' [ 'Graphics.Vega.VegaLite.ChX' ] ] -- enc = 'Graphics.Vega.VegaLite.encoding' -- . 'Graphics.Vega.VegaLite.position' 'Graphics.Vega.VegaLite.X' [ 'Graphics.Vega.VegaLite.PName' \"temperature\" -- , 'Graphics.Vega.VegaLite.PmType' 'Graphics.Vega.VegaLite.Quantitative' -- , 'Graphics.Vega.VegaLite.PBin' [ 'SelectionExtent' \"brush\" ] -- ] -- @ -- -- @since 0.5.0.0 | Step Double -- ^ The step size to use between bins. -- -- If specified, 'MaxBins' and other related options are ignored. | Steps [Double] -- ^ Pick the step size from this list. binProperty :: BinProperty -> LabelledSpec binProperty (AlreadyBinned b) = "binned" .= b binProperty (BinAnchor x) = "anchor" .= x binProperty (Base x) = "base" .= x binProperty (Divide xs) = "divide" .= xs binProperty (Extent mn mx) = "extent" .= [ mn, mx ] binProperty (SelectionExtent s) = "extent" .= object [ "selection" .= s ] binProperty (MaxBins n) = "maxbins" .= n binProperty (MinStep x) = "minstep" .= x binProperty (Nice b) = "nice" .= b binProperty (Step x) = "step" .= x binProperty (Steps xs) = "steps" .= xs bin :: [BinProperty] -> LabelledSpec bin [] = "bin" .= True bin xs = "bin" .= object (map binProperty xs) binned_ :: LabelledSpec binned_ = "bin" .= fromT "binned" -- | Properties for a window transform. -- -- @since 0.4.0.0 data WindowProperty = WFrame (Maybe Int) (Maybe Int) -- ^ Moving window for use by a window transform. When a number is -- given, via @Just@, then it indicates the offset from the current -- data object. A @Nothing@ indicates an un-bounded number of rows -- preceding or following the current data object. | WIgnorePeers Bool -- ^ Should the sliding window in a window transform ignore peer -- values (those considered identical by the sort criteria). | WGroupBy [FieldName] -- ^ The fields for partitioning data objects in a window transform -- into separate windows. If not specified, all points will be in a -- single group. | WSort [SortField] -- ^ Comparator for sorting data objects within a window transform. -- This is different to how Elm's VegaLite handles this (as of version 1.12.0) -- Helpers for windowPropertySpec wpFrame , wpIgnorePeers, wpGroupBy, wpSort :: WindowProperty -> Maybe VLSpec wpFrame (WFrame m1 m2) = Just (toJSON [allowNull m1, allowNull m2]) wpFrame _ = Nothing wpIgnorePeers (WIgnorePeers b) = Just (toJSON b) wpIgnorePeers _ = Nothing wpGroupBy (WGroupBy fs) = Just (toJSON fs) wpGroupBy _ = Nothing wpSort (WSort sfs) = Just (toJSON (map sortFieldSpec sfs)) wpSort _ = Nothing windowTS :: [([Window], FieldName)] -> [WindowProperty] -> TransformSpec windowTS wss wps = let addField n a = case mapMaybe a wps of [x] -> [n .= x] _ -> [] winFieldDef (ws, out) = object ("as" .= out : map windowFieldProperty ws) fields = [ "window" .= map winFieldDef wss ] <> addField "frame" wpFrame <> addField "ignorePeers" wpIgnorePeers <> addField "groupby" wpGroupBy <> addField "sort" wpSort in TS (object fields) joinAggregateTS :: [VLSpec] -> [WindowProperty] -> TransformSpec joinAggregateTS ops wps = let addField n a = case mapMaybe a wps of [x] -> [n .= x] _ -> [] fields = [ "joinaggregate" .= ops ] <> addField "frame" wpFrame <> addField "ignorePeers" wpIgnorePeers <> addField "groupby" wpGroupBy <> addField "sort" wpSort in TS (object fields) -- | This is used with 'Graphics.Vega.VegaLite.impute' and 'Graphics.Vega.VegaLite.PImpute'. -- -- @since 0.4.0.0 data ImputeProperty = ImFrame (Maybe Int) (Maybe Int) -- ^ 1d window over which data imputation values are generated. The two -- parameters should either be @Just@ a number indicating the offset from the current -- data object, or @Nothing@ to indicate unbounded rows preceding or following the -- current data object. | ImKeyVals DataValues -- ^ Key values to be considered for imputation. | ImKeyValSequence Double Double Double -- ^ Key values to be considered for imputation as a sequence of numbers between -- a start (first parameter), to less than an end (second parameter) in steps of -- the third parameter. | ImMethod ImMethod -- ^ How is the imputed value constructed. -- -- When using @ImMethod 'ImValue'@, the replacement value is -- set with 'ImNewValue'. | ImGroupBy [FieldName] -- ^ Allow imputing of missing values on a per-group basis. For use with the impute -- transform only and not a channel encoding. | ImNewValue DataValue -- ^ The replacement value (when using @ImMethod 'ImValue'@). imputeProperty :: ImputeProperty -> LabelledSpec imputeProperty (ImFrame m1 m2) = "frame" .= map allowNull [m1, m2] imputeProperty (ImKeyVals dVals) = "keyvals" .= dataValuesSpecs dVals imputeProperty (ImKeyValSequence start stop step) = "keyvals" .= object ["start" .= start, "stop" .= stop, "step" .= step] imputeProperty (ImMethod method) = "method" .= imMethodLabel method imputeProperty (ImNewValue dVal) = "value" .= dataValueSpec dVal imputeProperty (ImGroupBy _) = "groupby" .= A.Null imputePropertySpecFrame, imputePropertySpecKeyVals, imputePropertySpecKeyValSequence, imputePropertySpecGroupBy, imputePropertySpecMethod, imputePropertySpecValue :: ImputeProperty -> Maybe VLSpec imputePropertySpecFrame (ImFrame m1 m2) = Just (toJSON (map allowNull [m1, m2])) imputePropertySpecFrame _ = Nothing imputePropertySpecKeyVals (ImKeyVals dVals) = Just (toJSON (dataValuesSpecs dVals)) imputePropertySpecKeyVals _ = Nothing imputePropertySpecKeyValSequence (ImKeyValSequence start stop step) = let obj = ["start" .= start, "stop" .= stop, "step" .= step] in Just (object obj) imputePropertySpecKeyValSequence _ = Nothing imputePropertySpecGroupBy (ImGroupBy fields) = Just (toJSON fields) imputePropertySpecGroupBy _ = Nothing imputePropertySpecMethod (ImMethod method) = Just (toJSON (imMethodLabel method)) imputePropertySpecMethod _ = Nothing imputePropertySpecValue (ImNewValue dVal) = Just (dataValueSpec dVal) imputePropertySpecValue _ = Nothing impute_ :: [ImputeProperty] -> LabelledSpec impute_ ips = "impute" .= object (map imputeProperty ips) imputeTS :: FieldName -- ^ The data field to process. -> FieldName -- ^ The key field to uniquely identify data objects within a group. -> [ImputeProperty] -- ^ Define how the imputation works. -> TransformSpec imputeTS field key imProps = let addField n a = case mapMaybe a imProps of [x] -> [n .= x] _ -> [] fields = [ "impute" .= field , "key" .= key ] <> addField "frame" imputePropertySpecFrame -- TODO: can we combine the keyvals options? <> addField "keyvals" imputePropertySpecKeyVals <> addField "keyvals" imputePropertySpecKeyValSequence <> addField "method" imputePropertySpecMethod <> addField "groupby" imputePropertySpecGroupBy <> addField "value" imputePropertySpecValue in TS (object fields) -- | Imputation method to use when replacing values. -- -- @since 0.4.0.0 data ImMethod = ImMin -- ^ Use the minimum value. | ImMax -- ^ Use the maximum value. | ImMean -- ^ Use the mean value. | ImMedian -- ^ Use the median value. | ImValue -- ^ Use a replacement value (set with @ImNewValue@). imMethodLabel :: ImMethod -> T.Text imMethodLabel ImMin = "min" imMethodLabel ImMax = "max" imMethodLabel ImMean = "mean" imMethodLabel ImMedian = "median" imMethodLabel ImValue = "value"