diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,15 @@
 # Changelog
 
+## 0.2.0
+
+### Summary
+
+Updated the top-level module name.
+
+### API Changes
+
+NBA.Stats -> Data.NBA.Stats
+
 ## 0.1.0
 
 ### Summary
diff --git a/kawhi.cabal b/kawhi.cabal
--- a/kawhi.cabal
+++ b/kawhi.cabal
@@ -1,5 +1,5 @@
 name: kawhi
-version: 0.1.0
+version: 0.2.0
 synopsis: stats.NBA.com library
 description: Functions and types for interacting with stats.NBA.com
 homepage: https://github.com/hamsterdam/kawhi
@@ -20,7 +20,7 @@
   hs-source-dirs: library
   exposed-modules:
     Control.Monad.Http
-    NBA.Stats
+    Data.NBA.Stats
   other-modules:
   build-depends:
     base >= 4.8 && < 5,
@@ -41,7 +41,7 @@
   type: exitcode-stdio-1.0
   hs-source-dirs: tests
   main-is: Tests.hs
-  other-modules: NBA.Stats.Tests
+  other-modules: Data.NBA.Stats.Tests
   build-depends:
     base >= 4.8 && < 5,
     kawhi >= 0.0,
diff --git a/library/Data/NBA/Stats.hs b/library/Data/NBA/Stats.hs
new file mode 100644
--- /dev/null
+++ b/library/Data/NBA/Stats.hs
@@ -0,0 +1,333 @@
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+
+{-|
+    Module: Data.NBA.Stats
+    Copyright: Aaron Taylor, 2016
+    License: MIT
+    Maintainer: aaron@hamsterdam.co
+
+    Functions and types for interacting with <http://stats.NBA.com NBA Stats>.
+-}
+module Data.NBA.Stats (
+    -- * How to use this library
+    -- $use
+
+    -- * Simple API
+    getSplitRows,
+    getSplitRow,
+
+    -- * Generic API
+    getSplitRowsGeneric,
+    getSplitRowGeneric,
+
+    -- * Types
+    Stats(..),
+    Split(..),
+    SplitName,
+    SplitColumn,
+    SplitRow,
+    StatsPath,
+    StatsParameters,
+    StatsError(..),
+) where
+
+import qualified Control.Monad.Catch as Catch
+import qualified Control.Monad.Except as Except
+import qualified Control.Monad.Trans as Trans
+import qualified Control.Monad.Http as MonadHttp
+import qualified Data.Aeson as Aeson
+import qualified Data.Aeson.Types as Aeson
+import Data.Aeson ((.:), (.=))
+import qualified Data.ByteString.Lazy as LBS
+import qualified Data.ByteString as SBS
+import qualified Data.ByteString.Char8 as Char8
+import qualified Data.List as List
+import Data.Monoid ((<>))
+import qualified Data.Text as Text
+import qualified Network.HTTP.Client as HTTP
+import qualified Safe
+
+{- |
+    Gets all the rows in a NBA Stats split.
+
+    When using this function in a custom monad transformer, it may be desirable to use the generic version of this function, 'getSplitRowsGeneric', instead.
+-}
+getSplitRows ::
+    (Aeson.FromJSON a)
+    => StatsPath -- ^ The URL path for the stats web page containing the split.
+    -> SplitName -- ^ The split name.
+    -> StatsParameters -- ^ The parameters for customizing the stats.
+    -> IO (Either StatsError [a]) -- ^ The return value: an IO action resulting in an error or split rows.
+getSplitRows path splitName params = Except.runExceptT $ getSplitRowsGeneric path splitName params
+
+{- |
+    Gets a row in a NBA Stats split.
+
+    When using this function in a custom monad transformer, it may be desirable to use the generic version of this function, 'getSplitRowGeneric', instead.
+-}
+getSplitRow ::
+    (Eq v, Show v, Aeson.FromJSON v, Aeson.FromJSON a)
+    => StatsPath -- ^ The URL path for the stats web page containing the split.
+    -> SplitName -- ^ The split name.
+    -> SplitColumn -- ^ The column name key for a the desired row.
+    -> v -- ^ The expected row value associated with the column name key for a the desired row.
+    -> StatsParameters -- ^ The parameters for customizing the stats.
+    -> IO (Either StatsError a) -- ^ The return value: an IO action resulting in an error or a split row.
+getSplitRow path splitName key value params = Except.runExceptT $ getSplitRowGeneric path splitName key value params
+
+{- |
+    Gets all the rows in a NBA Stats split.
+
+    The simpler version of this function, 'getSplitRows', has a concrete 'm'.
+-}
+getSplitRowsGeneric ::
+    (Trans.MonadIO m, MonadHttp.MonadHttp m, Except.MonadError StatsError m, Catch.MonadThrow m, Aeson.FromJSON a)
+    => StatsPath -- ^ The URL path for the stats web page containing the split.
+    -> SplitName -- ^ The split name.
+    -> StatsParameters -- ^ The parameters for customizing the stats.
+    -> m [a] -- ^ The return value: an action resulting in an error or split rows.
+getSplitRowsGeneric path splitName params = do
+    response <- get path params
+    split <- findSplit response splitName
+    traverse (parseSplitRow $ columns split) $ rows split
+
+{- |
+    Gets a row in an NBA Stats split.
+
+    The simpler version of this function, 'getSplitRows', has a concrete 'm'.
+-}
+getSplitRowGeneric ::
+    (Trans.MonadIO m, MonadHttp.MonadHttp m, Except.MonadError StatsError m, Catch.MonadThrow m, Eq v, Show v, Aeson.FromJSON v, Aeson.FromJSON a)
+    => StatsPath -- ^ The URL path for the stats web page containing the split.
+    -> SplitName -- ^ The split name.
+    -> SplitColumn -- ^ The column name key for a the desired row.
+    -> v -- ^ The expected row value associated with the column name key for a the desired row.
+    -> StatsParameters -- ^ The parameters for customizing the stats.
+    -> m a -- ^ The return value: an action resulting in an error or a split row.
+getSplitRowGeneric path splitName key value params = do
+    response <- get path params
+    split <- findSplit response splitName
+    keyIndex <- maybe
+        (Except.throwError $ SplitColumnNameNotFound $ Text.unpack key)
+        return
+        (List.elemIndex key (columns split))
+    row <- maybe
+        (Except.throwError $ SplitKeyNotFound $ show value)
+        return
+        (List.find
+            (\row -> maybe
+                False
+                (==value)
+                (Safe.atMay row keyIndex >>= Aeson.parseMaybe Aeson.parseJSON))
+            (rows split))
+    parseSplitRow (columns split) row
+
+{- |
+    An NBA Stats split.
+
+    This type represents splits available from NBA Stats.
+-}
+data Split =
+    -- | Constructor for a split.
+    Split {
+        -- | The split's name.
+        name :: SplitName,
+        -- | The split's column names.
+        columns :: [SplitColumn],
+        -- | The split's rows of data.
+        rows :: [SplitRow]
+    }
+    deriving (Show, Eq)
+
+instance Aeson.FromJSON Split where
+    parseJSON (Aeson.Object v) = do
+        name <- v .: "name"
+        columns <- v .: "headers"
+        rows <- v .: "rowSet"
+        return Split {..}
+    parseJSON invalid = Aeson.typeMismatch "Split" invalid
+
+instance Aeson.ToJSON Split where
+    toJSON Split {..} = Aeson.object [
+        "name" .= name,
+        "headers" .= columns,
+        "rowSet" .= rows]
+
+-- | An NBA Stats split name.
+type SplitName = Text.Text
+
+-- | A column name in an NBA Stats split.
+type SplitColumn = Text.Text
+
+-- | A row of data in an NBA Stats split.
+type SplitRow = [Aeson.Value]
+
+{- |
+    An NBA Stats resource.
+
+    This type represents the top-level JSON object returned from the NBA Stats REST API.
+-}
+data Stats =
+    -- | Constructor for stats resource.
+    Stats {
+        -- | The resource's splits.
+        splits :: [Split]
+    }
+    deriving (Show, Eq)
+
+instance Aeson.ToJSON Stats where
+    toJSON Stats {..} = Aeson.object [
+        "resultSets" .= splits]
+
+instance Aeson.FromJSON Stats where
+    parseJSON (Aeson.Object o) = do
+        splits <- o .: "resultSets"
+        return Stats {..}
+    parseJSON invalid = Aeson.typeMismatch "Stats" invalid
+
+-- | A URL path for an NBA Stats resource.
+type StatsPath = SBS.ByteString
+
+-- | A collection of parameters that customize NBA Stats resources.
+type StatsParameters = [(SBS.ByteString, Maybe SBS.ByteString)]
+
+{- |
+    An error which may be generated by this library.
+-}
+data StatsError =
+    -- | An HTTP response has invalid JSON.
+    StatsResponseDecodeFailure String |
+    -- | A stats resource does not have a split matching the given split name.
+    SplitNameNotFound String |
+    -- | A split does not have a row matching the given column key and row value.
+    SplitKeyNotFound String |
+    -- | A split row has a different cardinality than the associated columns.
+    SplitRowCardinalityInconsistent String |
+    -- | A split does not have a column name matching the given key.
+    SplitColumnNameNotFound String |
+    -- | A failure to parse a split row's tabular data to the destination type.
+    SplitRowParseFailure String
+    deriving (Eq)
+
+instance Show StatsError where
+    show statsError = "StatsError (" ++ showCase statsError ++ ")"
+        where
+            showCase err = case err of
+                StatsResponseDecodeFailure message -> format "StatsResponseDecodeFailure" message
+                SplitNameNotFound message -> format "SplitNameNotFound" message
+                SplitKeyNotFound message -> format "SplitKeyNotFound" message
+                SplitRowCardinalityInconsistent message -> format "SplitRowCardinalityInconsistent" message
+                SplitColumnNameNotFound message -> format "SplitColumnNameNotFound" message
+                SplitRowParseFailure message -> format "SplitRowParseFailure" message
+            format :: String -> String -> String
+            format name message = name ++ " " ++ message
+
+parseSplitRow :: (Except.MonadError StatsError m, Aeson.FromJSON a) => [SplitColumn] -> SplitRow -> m a
+parseSplitRow columns row =
+    if length columns == length row
+        then case Aeson.parse Aeson.parseJSON $ Aeson.object (zip columns row) of
+            Aeson.Error message -> Except.throwError $ SplitRowParseFailure message
+            Aeson.Success split -> return split
+        else Except.throwError $ SplitRowCardinalityInconsistent $ show row
+
+findSplit :: (Except.MonadError StatsError m) => HTTP.Response LBS.ByteString -> SplitName -> m Split
+findSplit response splitName = do
+    stats <- either
+        (Except.throwError . StatsResponseDecodeFailure)
+        return
+        (Aeson.eitherDecode . HTTP.responseBody $ response)
+    maybe
+        (Except.throwError $ SplitNameNotFound $ Text.unpack splitName)
+        return
+        (List.find (\r -> name r == splitName) $ splits stats)
+
+
+
+get :: (MonadHttp.MonadHttp m, Catch.MonadThrow m) => StatsPath -> StatsParameters -> m (HTTP.Response LBS.ByteString)
+get path params = do
+    request <- HTTP.parseRequest $ Char8.unpack $ "http://stats.nba.com/stats/" <> path
+    MonadHttp.performRequest $ HTTP.setQueryString params request
+
+{- $use
+    The following is a working example of getting some "advanced statistics", split by month, for the San Antonio Spurs 2015-2016 regular season.
+
+    To learn how to find the NBA Stats values, like 'teamdashboardbygeneralsplits', for this example, read the <https://github.com/hamsterdam/kawhi/blob/master/guide.md guide>.
+
+    @
+    import qualified Data.Aeson as Aeson
+    import qualified Data.Aeson.Types as Aeson
+    import Data.Aeson ((.:))
+    import qualified Data.NBA.Stats as Stats
+
+    main :: IO ()
+    main = do
+        eitherErrorOrStats <- advancedStatsByMonth
+        case eitherErrorOrStats of
+            Left statsError -> print statsError
+            Right stats -> mapM_ print stats
+
+    data AdvancedStats = AdvancedStats {
+        month :: String,
+        offensiveRating :: Double,
+        defensiveRating :: Double
+    } deriving (Show, Eq)
+
+    instance Aeson.FromJSON AdvancedStats where
+        parseJSON (Aeson.Object o) = do
+            month <- o .: \"SEASON_MONTH_NAME\"
+            offensiveRating <- o .: \"OFF_RATING\"
+            defensiveRating <- o .: \"DEF_RATING\"
+            return AdvancedStats {..}
+        parseJSON invalid = Aeson.typeMismatch \"AdvancedStats\" invalid
+
+    advancedStatsByMonth :: IO (Either Stats.StatsError [AdvancedStats])
+    advancedStatsByMonth = Stats.getSplitRows \"teamdashboardbygeneralsplits\" \"MonthTeamDashboard\"
+        [
+            (\"Conference\", Nothing),
+            (\"DateFrom\", Nothing),
+            (\"DateTo\", Nothing),
+            (\"Division\", Nothing),
+            (\"GameScope\", Nothing),
+            (\"GameSegment\", Nothing),
+            (\"LastNGames\", Just \"0\"),
+            (\"LeagueID\", Just \"00\"),
+            (\"Location\", Nothing),
+            (\"MeasureType\", Just \"Advanced\"),
+            (\"Month\", Just \"0\"),
+            (\"OpponentTeamID\", Just \"0\"),
+            (\"Outcome\", Nothing),
+            (\"PaceAdjust\", Just \"N\"),
+            (\"PerMode\", Just \"PerGame\"),
+            (\"Period\", Just \"0\"),
+            (\"PlayerExperience\", Nothing),
+            (\"PlayerPosition\", Nothing),
+            (\"PlusMinus\", Just \"N\"),
+            (\"PORound\", Just \"0\"),
+            (\"Rank\", Just \"N\"),
+            (\"Season\", Just \"2015-16\"),
+            (\"SeasonSegment\", Nothing),
+            (\"SeasonType\", Just \"Regular Season\"),
+            (\"ShotClockRange\", Nothing),
+            (\"StarterBench\", Nothing),
+            (\"TeamID\", Just \"1610612759\"),
+            (\"VsConference\", Nothing),
+            (\"VsDivision\", Nothing)
+        ]
+    @
+
+    This program's output at the time of writing is:
+
+    @
+    AdvancedStats {month = \"October\", offensiveRating = 102.7, defensiveRating = 93.4}
+    AdvancedStats {month = \"November\", offensiveRating = 102.5, defensiveRating = 93.4}
+    AdvancedStats {month = \"December\", offensiveRating = 111.8, defensiveRating = 91.5}
+    AdvancedStats {month = \"January\", offensiveRating = 114.0, defensiveRating = 100.7}
+    AdvancedStats {month = \"February\", offensiveRating = 110.7, defensiveRating = 99.1}
+    AdvancedStats {month = \"March\", offensiveRating = 107.8, defensiveRating = 97.2}
+    AdvancedStats {month = \"April\", offensiveRating = 102.3, defensiveRating = 103.5}
+    @
+-}
diff --git a/library/NBA/Stats.hs b/library/NBA/Stats.hs
deleted file mode 100644
--- a/library/NBA/Stats.hs
+++ /dev/null
@@ -1,333 +0,0 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RecordWildCards #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TypeSynonymInstances #-}
-
-{-|
-    Module: NBA.Stats
-    Copyright: Aaron Taylor, 2016
-    License: MIT
-    Maintainer: aaron@hamsterdam.co
-
-    Functions and types for interacting with <http://stats.NBA.com NBA Stats>.
--}
-module NBA.Stats (
-    -- * How to use this library
-    -- $use
-
-    -- * Simple API
-    getSplitRows,
-    getSplitRow,
-
-    -- * Generic API
-    getSplitRowsGeneric,
-    getSplitRowGeneric,
-
-    -- * Types
-    Stats(..),
-    Split(..),
-    SplitName,
-    SplitColumn,
-    SplitRow,
-    StatsPath,
-    StatsParameters,
-    StatsError(..),
-) where
-
-import qualified Control.Monad.Catch as Catch
-import qualified Control.Monad.Except as Except
-import qualified Control.Monad.Trans as Trans
-import qualified Control.Monad.Http as MonadHttp
-import qualified Data.Aeson as Aeson
-import qualified Data.Aeson.Types as Aeson
-import Data.Aeson ((.:), (.=))
-import qualified Data.ByteString.Lazy as LBS
-import qualified Data.ByteString as SBS
-import qualified Data.ByteString.Char8 as Char8
-import qualified Data.List as List
-import Data.Monoid ((<>))
-import qualified Data.Text as Text
-import qualified Network.HTTP.Client as HTTP
-import qualified Safe
-
-{- |
-    Gets all the rows in a NBA Stats split.
-
-    When using this function in a custom monad transformer, it may be desirable to use the generic version of this function, 'getSplitRowsGeneric', instead.
--}
-getSplitRows ::
-    (Aeson.FromJSON a)
-    => StatsPath -- ^ The URL path for the stats web page containing the split.
-    -> SplitName -- ^ The split name.
-    -> StatsParameters -- ^ The parameters for customizing the stats.
-    -> IO (Either StatsError [a]) -- ^ The return value: an IO action resulting in an error or split rows.
-getSplitRows path splitName params = Except.runExceptT $ getSplitRowsGeneric path splitName params
-
-{- |
-    Gets a row in a NBA Stats split.
-
-    When using this function in a custom monad transformer, it may be desirable to use the generic version of this function, 'getSplitRowGeneric', instead.
--}
-getSplitRow ::
-    (Eq v, Show v, Aeson.FromJSON v, Aeson.FromJSON a)
-    => StatsPath -- ^ The URL path for the stats web page containing the split.
-    -> SplitName -- ^ The split name.
-    -> SplitColumn -- ^ The column name key for a the desired row.
-    -> v -- ^ The expected row value associated with the column name key for a the desired row.
-    -> StatsParameters -- ^ The parameters for customizing the stats.
-    -> IO (Either StatsError a) -- ^ The return value: an IO action resulting in an error or a split row.
-getSplitRow path splitName key value params = Except.runExceptT $ getSplitRowGeneric path splitName key value params
-
-{- |
-    Gets all the rows in a NBA Stats split.
-
-    The simpler version of this function, 'getSplitRows', has a concrete 'm'.
--}
-getSplitRowsGeneric ::
-    (Trans.MonadIO m, MonadHttp.MonadHttp m, Except.MonadError StatsError m, Catch.MonadThrow m, Aeson.FromJSON a)
-    => StatsPath -- ^ The URL path for the stats web page containing the split.
-    -> SplitName -- ^ The split name.
-    -> StatsParameters -- ^ The parameters for customizing the stats.
-    -> m [a] -- ^ The return value: an action resulting in an error or split rows.
-getSplitRowsGeneric path splitName params = do
-    response <- get path params
-    split <- findSplit response splitName
-    traverse (parseSplitRow $ columns split) $ rows split
-
-{- |
-    Gets a row in an NBA Stats split.
-
-    The simpler version of this function, 'getSplitRows', has a concrete 'm'.
--}
-getSplitRowGeneric ::
-    (Trans.MonadIO m, MonadHttp.MonadHttp m, Except.MonadError StatsError m, Catch.MonadThrow m, Eq v, Show v, Aeson.FromJSON v, Aeson.FromJSON a)
-    => StatsPath -- ^ The URL path for the stats web page containing the split.
-    -> SplitName -- ^ The split name.
-    -> SplitColumn -- ^ The column name key for a the desired row.
-    -> v -- ^ The expected row value associated with the column name key for a the desired row.
-    -> StatsParameters -- ^ The parameters for customizing the stats.
-    -> m a -- ^ The return value: an action resulting in an error or a split row.
-getSplitRowGeneric path splitName key value params = do
-    response <- get path params
-    split <- findSplit response splitName
-    keyIndex <- maybe
-        (Except.throwError $ SplitColumnNameNotFound $ Text.unpack key)
-        return
-        (List.elemIndex key (columns split))
-    row <- maybe
-        (Except.throwError $ SplitKeyNotFound $ show value)
-        return
-        (List.find
-            (\row -> maybe
-                False
-                (==value)
-                (Safe.atMay row keyIndex >>= Aeson.parseMaybe Aeson.parseJSON))
-            (rows split))
-    parseSplitRow (columns split) row
-
-{- |
-    An NBA Stats split.
-
-    This type represents splits available from NBA Stats.
--}
-data Split =
-    -- | Constructor for a split.
-    Split {
-        -- | The split's name.
-        name :: SplitName,
-        -- | The split's column names.
-        columns :: [SplitColumn],
-        -- | The split's rows of data.
-        rows :: [SplitRow]
-    }
-    deriving (Show, Eq)
-
-instance Aeson.FromJSON Split where
-    parseJSON (Aeson.Object v) = do
-        name <- v .: "name"
-        columns <- v .: "headers"
-        rows <- v .: "rowSet"
-        return Split {..}
-    parseJSON invalid = Aeson.typeMismatch "Split" invalid
-
-instance Aeson.ToJSON Split where
-    toJSON Split {..} = Aeson.object [
-        "name" .= name,
-        "headers" .= columns,
-        "rowSet" .= rows]
-
--- | An NBA Stats split name.
-type SplitName = Text.Text
-
--- | A column name in an NBA Stats split.
-type SplitColumn = Text.Text
-
--- | A row of data in an NBA Stats split.
-type SplitRow = [Aeson.Value]
-
-{- |
-    An NBA Stats resource.
-
-    This type represents the top-level JSON object returned from the NBA Stats REST API.
--}
-data Stats =
-    -- | Constructor for stats resource.
-    Stats {
-        -- | The resource's splits.
-        splits :: [Split]
-    }
-    deriving (Show, Eq)
-
-instance Aeson.ToJSON Stats where
-    toJSON Stats {..} = Aeson.object [
-        "resultSets" .= splits]
-
-instance Aeson.FromJSON Stats where
-    parseJSON (Aeson.Object o) = do
-        splits <- o .: "resultSets"
-        return Stats {..}
-    parseJSON invalid = Aeson.typeMismatch "Stats" invalid
-
--- | A URL path for an NBA Stats resource.
-type StatsPath = SBS.ByteString
-
--- | A collection of parameters that customize NBA Stats resources.
-type StatsParameters = [(SBS.ByteString, Maybe SBS.ByteString)]
-
-{- |
-    An error which may be generated by this library.
--}
-data StatsError =
-    -- | An HTTP response has invalid JSON.
-    StatsResponseDecodeFailure String |
-    -- | A stats resource does not have a split matching the given split name.
-    SplitNameNotFound String |
-    -- | A split does not have a row matching the given column key and row value.
-    SplitKeyNotFound String |
-    -- | A split row has a different cardinality than the associated columns.
-    SplitRowCardinalityInconsistent String |
-    -- | A split does not have a column name matching the given key.
-    SplitColumnNameNotFound String |
-    -- | A failure to parse a split row's tabular data to the destination type.
-    SplitRowParseFailure String
-    deriving (Eq)
-
-instance Show StatsError where
-    show statsError = "StatsError (" ++ showCase statsError ++ ")"
-        where
-            showCase err = case err of
-                StatsResponseDecodeFailure message -> format "StatsResponseDecodeFailure" message
-                SplitNameNotFound message -> format "SplitNameNotFound" message
-                SplitKeyNotFound message -> format "SplitKeyNotFound" message
-                SplitRowCardinalityInconsistent message -> format "SplitRowCardinalityInconsistent" message
-                SplitColumnNameNotFound message -> format "SplitColumnNameNotFound" message
-                SplitRowParseFailure message -> format "SplitRowParseFailure" message
-            format :: String -> String -> String
-            format name message = name ++ " " ++ message
-
-parseSplitRow :: (Except.MonadError StatsError m, Aeson.FromJSON a) => [SplitColumn] -> SplitRow -> m a
-parseSplitRow columns row =
-    if length columns == length row
-        then case Aeson.parse Aeson.parseJSON $ Aeson.object (zip columns row) of
-            Aeson.Error message -> Except.throwError $ SplitRowParseFailure message
-            Aeson.Success split -> return split
-        else Except.throwError $ SplitRowCardinalityInconsistent $ show row
-
-findSplit :: (Except.MonadError StatsError m) => HTTP.Response LBS.ByteString -> SplitName -> m Split
-findSplit response splitName = do
-    stats <- either
-        (Except.throwError . StatsResponseDecodeFailure)
-        return
-        (Aeson.eitherDecode . HTTP.responseBody $ response)
-    maybe
-        (Except.throwError $ SplitNameNotFound $ Text.unpack splitName)
-        return
-        (List.find (\r -> name r == splitName) $ splits stats)
-
-
-
-get :: (MonadHttp.MonadHttp m, Catch.MonadThrow m) => StatsPath -> StatsParameters -> m (HTTP.Response LBS.ByteString)
-get path params = do
-    request <- HTTP.parseRequest $ Char8.unpack $ "http://stats.nba.com/stats/" <> path
-    MonadHttp.performRequest $ HTTP.setQueryString params request
-
-{- $use
-    The following is a working example of getting some "advanced statistics", split by month, for the San Antonio Spurs 2015-2016 regular season.
-
-    To learn how to find the NBA Stats values, like 'teamdashboardbygeneralsplits', for this example, read the <https://github.com/hamsterdam/kawhi/blob/master/guide.md guide>.
-
-    @
-    import qualified Data.Aeson as Aeson
-    import qualified Data.Aeson.Types as Aeson
-    import Data.Aeson ((.:))
-    import qualified NBA.Stats as Stats
-
-    main :: IO ()
-    main = do
-        eitherErrorOrStats <- advancedStatsByMonth
-        case eitherErrorOrStats of
-            Left statsError -> print statsError
-            Right stats -> mapM_ print stats
-
-    data AdvancedStats = AdvancedStats {
-        month :: String,
-        offensiveRating :: Double,
-        defensiveRating :: Double
-    } deriving (Show, Eq)
-
-    instance Aeson.FromJSON AdvancedStats where
-        parseJSON (Aeson.Object o) = do
-            month <- o .: \"SEASON_MONTH_NAME\"
-            offensiveRating <- o .: \"OFF_RATING\"
-            defensiveRating <- o .: \"DEF_RATING\"
-            return AdvancedStats {..}
-        parseJSON invalid = Aeson.typeMismatch \"AdvancedStats\" invalid
-
-    advancedStatsByMonth :: IO (Either Stats.StatsError [AdvancedStats])
-    advancedStatsByMonth = Stats.getSplitRows \"teamdashboardbygeneralsplits\" \"MonthTeamDashboard\"
-        [
-            (\"Conference\", Nothing),
-            (\"DateFrom\", Nothing),
-            (\"DateTo\", Nothing),
-            (\"Division\", Nothing),
-            (\"GameScope\", Nothing),
-            (\"GameSegment\", Nothing),
-            (\"LastNGames\", Just \"0\"),
-            (\"LeagueID\", Just \"00\"),
-            (\"Location\", Nothing),
-            (\"MeasureType\", Just \"Advanced\"),
-            (\"Month\", Just \"0\"),
-            (\"OpponentTeamID\", Just \"0\"),
-            (\"Outcome\", Nothing),
-            (\"PaceAdjust\", Just \"N\"),
-            (\"PerMode\", Just \"PerGame\"),
-            (\"Period\", Just \"0\"),
-            (\"PlayerExperience\", Nothing),
-            (\"PlayerPosition\", Nothing),
-            (\"PlusMinus\", Just \"N\"),
-            (\"PORound\", Just \"0\"),
-            (\"Rank\", Just \"N\"),
-            (\"Season\", Just \"2015-16\"),
-            (\"SeasonSegment\", Nothing),
-            (\"SeasonType\", Just \"Regular Season\"),
-            (\"ShotClockRange\", Nothing),
-            (\"StarterBench\", Nothing),
-            (\"TeamID\", Just \"1610612759\"),
-            (\"VsConference\", Nothing),
-            (\"VsDivision\", Nothing)
-        ]
-    @
-
-    This program's output at the time of writing is:
-
-    @
-    AdvancedStats {month = \"October\", offensiveRating = 102.7, defensiveRating = 93.4}
-    AdvancedStats {month = \"November\", offensiveRating = 102.5, defensiveRating = 93.4}
-    AdvancedStats {month = \"December\", offensiveRating = 111.8, defensiveRating = 91.5}
-    AdvancedStats {month = \"January\", offensiveRating = 114.0, defensiveRating = 100.7}
-    AdvancedStats {month = \"February\", offensiveRating = 110.7, defensiveRating = 99.1}
-    AdvancedStats {month = \"March\", offensiveRating = 107.8, defensiveRating = 97.2}
-    AdvancedStats {month = \"April\", offensiveRating = 102.3, defensiveRating = 103.5}
-    @
--}
diff --git a/tests/Data/NBA/Stats/Tests.hs b/tests/Data/NBA/Stats/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Data/NBA/Stats/Tests.hs
@@ -0,0 +1,190 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Data.NBA.Stats.Tests where
+
+import qualified Control.Monad.Except as Except
+import qualified Control.Monad.Http as MonadHttp
+import qualified Data.Aeson as Aeson
+import qualified Data.Aeson.Types as Aeson
+import Data.Aeson ((.:))
+import qualified Data.ByteString.Lazy as ByteString
+import Data.Monoid ((<>))
+import qualified Data.Scientific as Sci
+import qualified Data.Text as Text
+import qualified Network.HTTP.Client.Internal as HTTP
+import qualified Network.HTTP.Types as HTTP
+import qualified Data.NBA.Stats as Stats
+import qualified Test.Tasty as Tasty
+import qualified Test.Tasty.HUnit as HUnit
+import qualified Test.Tasty.SmallCheck as SC
+import Test.Tasty.HUnit ((@?=))
+
+tests :: Tasty.TestTree
+tests = Tasty.testGroup "Data.NBA.Stats" [
+    propertyStatsErrorShow Stats.StatsResponseDecodeFailure "StatsResponseDecodeFailure",
+    propertyStatsErrorShow Stats.SplitNameNotFound "SplitNameNotFound",
+    propertyStatsErrorShow Stats.SplitKeyNotFound "SplitKeyNotFound",
+    propertyStatsErrorShow Stats.SplitColumnNameNotFound "SplitColumnNameNotFound",
+    propertyStatsErrorShow Stats.SplitRowCardinalityInconsistent "SplitRowCardinalityInconsistent",
+    propertyStatsErrorShow Stats.SplitRowParseFailure "SplitRowParseFailure",
+
+    getSplitRowExpectSuccess
+        "Success"
+        (defaultResponseBody [defaultSplit])
+        defaultModel,
+
+    getSplitRowsExpectSuccess
+        "Success"
+        (defaultResponseBody [defaultSplit { Stats.rows = [defaultRow, defaultRow] }])
+        [defaultModel, defaultModel],
+
+    getSplitRowExpectFailure
+        "SplitRowCardinalityInconsistent"
+        (defaultResponseBody [defaultSplit { Stats.rows = [take 3 defaultRow] }])
+        (Stats.SplitRowCardinalityInconsistent $ show $ take 3 defaultRow),
+
+    getSplitRowExpectFailure
+        "SplitKeyNotFound (no rows)"
+        (defaultResponseBody [defaultSplit { Stats.rows = [] }])
+        (Stats.SplitKeyNotFound $ show defaultRowIdentifier),
+
+    getSplitRowExpectFailure
+        "SplitKeyNotFound (no key value)"
+        (defaultResponseBody [defaultSplit { Stats.rows = [[]] }])
+        (Stats.SplitKeyNotFound $ show defaultRowIdentifier),
+
+    getSplitRowExpectFailure
+        "SplitKeyNotFound (JSON parse error for key value)"
+        (let rowWithBadValue = Aeson.Number 99 : defaultRow in defaultResponseBody [defaultSplit { Stats.rows = [rowWithBadValue] }])
+        (Stats.SplitKeyNotFound $ show defaultRowIdentifier),
+
+    getSplitRowExpectFailure
+        "SplitNameNotFound"
+        (defaultResponseBody [])
+        (Stats.SplitNameNotFound $ Text.unpack defaultSplitName),
+
+    getSplitRowExpectFailure
+        "SplitColumnNameNotFound"
+        (defaultResponseBody [defaultSplit { Stats.columns = [] }])
+        (Stats.SplitColumnNameNotFound $ Text.unpack defaultColumnsKey),
+
+    getSplitRowExpectFailure
+        "SplitRowParseFailure (type mismatch)"
+        (defaultResponseBody [defaultSplit { Stats.rows = [[Aeson.String defaultRowIdentifier, Aeson.String $ Text.pack $ show (a defaultModel), Aeson.String $ b defaultModel, Aeson.Number $ Sci.fromFloatDigits (c defaultModel)]] }])
+        (Stats.SplitRowParseFailure "failed to parse field A: expected Integral, encountered String"),
+
+    getSplitRowExpectFailure
+        "SplitRowParseFailure (missing field)"
+        (defaultResponseBody [defaultSplit { Stats.columns = fmap (\c -> if c == "B" then "!B" else c) defaultColumns }])
+        (Stats.SplitRowParseFailure "key \"B\" not present"),
+
+    getSplitRowExpectFailure
+        "StatsResponseDecodeFailure (for Stats)"
+        (Aeson.encode [defaultSplit])
+        (Stats.StatsResponseDecodeFailure "Error in $: expected Stats, encountered Array"),
+
+    HUnit.testCase
+        "parseJSON invalid :: Parser Split -> Error"
+        (case Aeson.fromJSON $ Aeson.String "foo" :: Aeson.Result Stats.Split of
+            Aeson.Success _ -> HUnit.assertFailure "Parse should not have succeeded"
+            Aeson.Error e -> e @?= "expected Split, encountered String")
+    ]
+
+propertyStatsErrorShow :: Show a => (String -> a) -> String -> Tasty.TestTree
+propertyStatsErrorShow constructor exception =
+    SC.testProperty testName property
+    where
+        testName = "show (" ++ exception ++ " message) == \"StatsError (" ++ exception ++ " message)\""
+        property message = show (constructor message) == "StatsError (" ++ exception ++ " " ++ message ++ ")"
+
+getSplitRowExpectFailure :: Tasty.TestName -> ByteString.ByteString -> Stats.StatsError -> Tasty.TestTree
+getSplitRowExpectFailure testName responseBody expected = HUnit.testCase ("Get stat -> " <> testName) $ do
+    eitherModel <- runStatsTest $ runAction statAction responseBody HTTP.ok200
+    case eitherModel of
+        Left err -> err @?= expected
+        Right model -> HUnit.assertFailure $ show model
+
+expectSuccess :: (Eq a, Show a) => MonadHttp.HttpT StatsTest a -> Tasty.TestName -> ByteString.ByteString -> a -> Tasty.TestTree
+expectSuccess action testName responseBody expected =
+    HUnit.testCase testName $ do
+        eitherModel <- runStatsTest $ runAction action responseBody HTTP.ok200
+        case eitherModel of
+            Left err -> HUnit.assertFailure $ show err
+            Right actual -> actual @?= expected
+
+getSplitRowsExpectSuccess :: Tasty.TestName -> ByteString.ByteString -> [MockModel] -> Tasty.TestTree
+getSplitRowsExpectSuccess name = expectSuccess (Stats.getSplitRowsGeneric "mockmodels" defaultSplitName defaultParams) $ "Get stats -> " <> name
+
+getSplitRowExpectSuccess :: Tasty.TestName -> ByteString.ByteString -> MockModel -> Tasty.TestTree
+getSplitRowExpectSuccess name = expectSuccess statAction $ "Get stat -> " <> name
+
+statAction :: MonadHttp.HttpT StatsTest MockModel
+statAction = Stats.getSplitRowGeneric "mockmodels" defaultSplitName defaultColumnsKey defaultRowIdentifier defaultParams
+
+runAction :: MonadHttp.HttpT StatsTest a -> ByteString.ByteString -> HTTP.Status -> StatsTest a
+runAction action responseBody responseStatus = MonadHttp.runHttpT
+    action
+    HTTP.Response {
+        HTTP.responseStatus = responseStatus,
+        HTTP.responseVersion = HTTP.http11,
+        HTTP.responseHeaders = [],
+        HTTP.responseBody = responseBody,
+        HTTP.responseCookieJar = HTTP.createCookieJar [],
+        HTTP.responseClose' = HTTP.ResponseClose (return () :: IO ())
+    }
+
+defaultParams :: Stats.StatsParameters
+defaultParams = [("param", Just "value")]
+
+defaultResponseBody :: [Stats.Split] -> ByteString.ByteString
+defaultResponseBody splits = Aeson.encode defaultStats { Stats.splits = splits }
+
+defaultStats :: Stats.Stats
+defaultStats = Stats.Stats {
+    splits = []
+}
+
+defaultSplit :: Stats.Split
+defaultSplit = Stats.Split {
+    name = defaultSplitName,
+    columns = defaultColumns,
+    rows = [defaultRow]
+}
+
+defaultSplitName :: Stats.SplitName
+defaultSplitName = "name"
+
+defaultColumnsKey :: Stats.SplitColumn
+defaultColumnsKey = "key"
+
+defaultRowIdentifier :: Text.Text
+defaultRowIdentifier = "identifier"
+
+defaultColumns :: [Stats.SplitColumn]
+defaultColumns = [defaultColumnsKey, "A", "B", "C"]
+
+defaultRow :: Stats.SplitRow
+defaultRow = [Aeson.String defaultRowIdentifier, Aeson.Number $ Sci.scientific (a defaultModel) 0, Aeson.String $ b defaultModel, Aeson.Number $ Sci.fromFloatDigits (c defaultModel)]
+
+defaultModel :: MockModel
+defaultModel = MockModel { a = 1, b = "1", c = 1.1 }
+
+type StatsTest = Except.ExceptT Stats.StatsError IO
+
+runStatsTest :: StatsTest a -> IO (Either Stats.StatsError a)
+runStatsTest = Except.runExceptT
+
+data MockModel = MockModel {
+    a :: Integer,
+    b :: Text.Text,
+    c :: Double
+} deriving (Show, Eq)
+
+instance Aeson.FromJSON MockModel where
+    parseJSON (Aeson.Object o) = do
+        a <- o .: "A"
+        b <- o .: "B"
+        c <- o .: "C"
+        return MockModel {..}
+    parseJSON invalid = Aeson.typeMismatch "MockModel" invalid
diff --git a/tests/NBA/Stats/Tests.hs b/tests/NBA/Stats/Tests.hs
deleted file mode 100644
--- a/tests/NBA/Stats/Tests.hs
+++ /dev/null
@@ -1,190 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RecordWildCards #-}
-
-module NBA.Stats.Tests where
-
-import qualified Control.Monad.Except as Except
-import qualified Control.Monad.Http as MonadHttp
-import qualified Data.Aeson as Aeson
-import qualified Data.Aeson.Types as Aeson
-import Data.Aeson ((.:))
-import qualified Data.ByteString.Lazy as ByteString
-import Data.Monoid ((<>))
-import qualified Data.Scientific as Sci
-import qualified Data.Text as Text
-import qualified Network.HTTP.Client.Internal as HTTP
-import qualified Network.HTTP.Types as HTTP
-import qualified NBA.Stats as Stats
-import qualified Test.Tasty as Tasty
-import qualified Test.Tasty.HUnit as HUnit
-import qualified Test.Tasty.SmallCheck as SC
-import Test.Tasty.HUnit ((@?=))
-
-tests :: Tasty.TestTree
-tests = Tasty.testGroup "NBA.Stats" [
-    propertyStatsErrorShow Stats.StatsResponseDecodeFailure "StatsResponseDecodeFailure",
-    propertyStatsErrorShow Stats.SplitNameNotFound "SplitNameNotFound",
-    propertyStatsErrorShow Stats.SplitKeyNotFound "SplitKeyNotFound",
-    propertyStatsErrorShow Stats.SplitColumnNameNotFound "SplitColumnNameNotFound",
-    propertyStatsErrorShow Stats.SplitRowCardinalityInconsistent "SplitRowCardinalityInconsistent",
-    propertyStatsErrorShow Stats.SplitRowParseFailure "SplitRowParseFailure",
-
-    getSplitRowExpectSuccess
-        "Success"
-        (defaultResponseBody [defaultSplit])
-        defaultModel,
-
-    getSplitRowsExpectSuccess
-        "Success"
-        (defaultResponseBody [defaultSplit { Stats.rows = [defaultRow, defaultRow] }])
-        [defaultModel, defaultModel],
-
-    getSplitRowExpectFailure
-        "SplitRowCardinalityInconsistent"
-        (defaultResponseBody [defaultSplit { Stats.rows = [take 3 defaultRow] }])
-        (Stats.SplitRowCardinalityInconsistent $ show $ take 3 defaultRow),
-
-    getSplitRowExpectFailure
-        "SplitKeyNotFound (no rows)"
-        (defaultResponseBody [defaultSplit { Stats.rows = [] }])
-        (Stats.SplitKeyNotFound $ show defaultRowIdentifier),
-
-    getSplitRowExpectFailure
-        "SplitKeyNotFound (no key value)"
-        (defaultResponseBody [defaultSplit { Stats.rows = [[]] }])
-        (Stats.SplitKeyNotFound $ show defaultRowIdentifier),
-
-    getSplitRowExpectFailure
-        "SplitKeyNotFound (JSON parse error for key value)"
-        (let rowWithBadValue = Aeson.Number 99 : defaultRow in defaultResponseBody [defaultSplit { Stats.rows = [rowWithBadValue] }])
-        (Stats.SplitKeyNotFound $ show defaultRowIdentifier),
-
-    getSplitRowExpectFailure
-        "SplitNameNotFound"
-        (defaultResponseBody [])
-        (Stats.SplitNameNotFound $ Text.unpack defaultSplitName),
-
-    getSplitRowExpectFailure
-        "SplitColumnNameNotFound"
-        (defaultResponseBody [defaultSplit { Stats.columns = [] }])
-        (Stats.SplitColumnNameNotFound $ Text.unpack defaultColumnsKey),
-
-    getSplitRowExpectFailure
-        "SplitRowParseFailure (type mismatch)"
-        (defaultResponseBody [defaultSplit { Stats.rows = [[Aeson.String defaultRowIdentifier, Aeson.String $ Text.pack $ show (a defaultModel), Aeson.String $ b defaultModel, Aeson.Number $ Sci.fromFloatDigits (c defaultModel)]] }])
-        (Stats.SplitRowParseFailure "failed to parse field A: expected Integral, encountered String"),
-
-    getSplitRowExpectFailure
-        "SplitRowParseFailure (missing field)"
-        (defaultResponseBody [defaultSplit { Stats.columns = fmap (\c -> if c == "B" then "!B" else c) defaultColumns }])
-        (Stats.SplitRowParseFailure "key \"B\" not present"),
-
-    getSplitRowExpectFailure
-        "StatsResponseDecodeFailure (for Stats)"
-        (Aeson.encode [defaultSplit])
-        (Stats.StatsResponseDecodeFailure "Error in $: expected Stats, encountered Array"),
-
-    HUnit.testCase
-        "parseJSON invalid :: Parser Split -> Error"
-        (case Aeson.fromJSON $ Aeson.String "foo" :: Aeson.Result Stats.Split of
-            Aeson.Success _ -> HUnit.assertFailure "Parse should not have succeeded"
-            Aeson.Error e -> e @?= "expected Split, encountered String")
-    ]
-
-propertyStatsErrorShow :: Show a => (String -> a) -> String -> Tasty.TestTree
-propertyStatsErrorShow constructor exception =
-    SC.testProperty testName property
-    where
-        testName = "show (" ++ exception ++ " message) == \"StatsError (" ++ exception ++ " message)\""
-        property message = show (constructor message) == "StatsError (" ++ exception ++ " " ++ message ++ ")"
-
-getSplitRowExpectFailure :: Tasty.TestName -> ByteString.ByteString -> Stats.StatsError -> Tasty.TestTree
-getSplitRowExpectFailure testName responseBody expected = HUnit.testCase ("Get stat -> " <> testName) $ do
-    eitherModel <- runStatsTest $ runAction statAction responseBody HTTP.ok200
-    case eitherModel of
-        Left err -> err @?= expected
-        Right model -> HUnit.assertFailure $ show model
-
-expectSuccess :: (Eq a, Show a) => MonadHttp.HttpT StatsTest a -> Tasty.TestName -> ByteString.ByteString -> a -> Tasty.TestTree
-expectSuccess action testName responseBody expected =
-    HUnit.testCase testName $ do
-        eitherModel <- runStatsTest $ runAction action responseBody HTTP.ok200
-        case eitherModel of
-            Left err -> HUnit.assertFailure $ show err
-            Right actual -> actual @?= expected
-
-getSplitRowsExpectSuccess :: Tasty.TestName -> ByteString.ByteString -> [MockModel] -> Tasty.TestTree
-getSplitRowsExpectSuccess name = expectSuccess (Stats.getSplitRowsGeneric "mockmodels" defaultSplitName defaultParams) $ "Get stats -> " <> name
-
-getSplitRowExpectSuccess :: Tasty.TestName -> ByteString.ByteString -> MockModel -> Tasty.TestTree
-getSplitRowExpectSuccess name = expectSuccess statAction $ "Get stat -> " <> name
-
-statAction :: MonadHttp.HttpT StatsTest MockModel
-statAction = Stats.getSplitRowGeneric "mockmodels" defaultSplitName defaultColumnsKey defaultRowIdentifier defaultParams
-
-runAction :: MonadHttp.HttpT StatsTest a -> ByteString.ByteString -> HTTP.Status -> StatsTest a
-runAction action responseBody responseStatus = MonadHttp.runHttpT
-    action
-    HTTP.Response {
-        HTTP.responseStatus = responseStatus,
-        HTTP.responseVersion = HTTP.http11,
-        HTTP.responseHeaders = [],
-        HTTP.responseBody = responseBody,
-        HTTP.responseCookieJar = HTTP.createCookieJar [],
-        HTTP.responseClose' = HTTP.ResponseClose (return () :: IO ())
-    }
-
-defaultParams :: Stats.StatsParameters
-defaultParams = [("param", Just "value")]
-
-defaultResponseBody :: [Stats.Split] -> ByteString.ByteString
-defaultResponseBody splits = Aeson.encode defaultStats { Stats.splits = splits }
-
-defaultStats :: Stats.Stats
-defaultStats = Stats.Stats {
-    splits = []
-}
-
-defaultSplit :: Stats.Split
-defaultSplit = Stats.Split {
-    name = defaultSplitName,
-    columns = defaultColumns,
-    rows = [defaultRow]
-}
-
-defaultSplitName :: Stats.SplitName
-defaultSplitName = "name"
-
-defaultColumnsKey :: Stats.SplitColumn
-defaultColumnsKey = "key"
-
-defaultRowIdentifier :: Text.Text
-defaultRowIdentifier = "identifier"
-
-defaultColumns :: [Stats.SplitColumn]
-defaultColumns = [defaultColumnsKey, "A", "B", "C"]
-
-defaultRow :: Stats.SplitRow
-defaultRow = [Aeson.String defaultRowIdentifier, Aeson.Number $ Sci.scientific (a defaultModel) 0, Aeson.String $ b defaultModel, Aeson.Number $ Sci.fromFloatDigits (c defaultModel)]
-
-defaultModel :: MockModel
-defaultModel = MockModel { a = 1, b = "1", c = 1.1 }
-
-type StatsTest = Except.ExceptT Stats.StatsError IO
-
-runStatsTest :: StatsTest a -> IO (Either Stats.StatsError a)
-runStatsTest = Except.runExceptT
-
-data MockModel = MockModel {
-    a :: Integer,
-    b :: Text.Text,
-    c :: Double
-} deriving (Show, Eq)
-
-instance Aeson.FromJSON MockModel where
-    parseJSON (Aeson.Object o) = do
-        a <- o .: "A"
-        b <- o .: "B"
-        c <- o .: "C"
-        return MockModel {..}
-    parseJSON invalid = Aeson.typeMismatch "MockModel" invalid
diff --git a/tests/Tests.hs b/tests/Tests.hs
--- a/tests/Tests.hs
+++ b/tests/Tests.hs
@@ -1,6 +1,6 @@
 import qualified Test.Tasty as Tasty
 
-import qualified NBA.Stats.Tests as Stats
+import qualified Data.NBA.Stats.Tests as Stats
 
 main :: IO ()
 main = Tasty.defaultMain Stats.tests
