diff --git a/Data/Kanji.hs b/Data/Kanji.hs
--- a/Data/Kanji.hs
+++ b/Data/Kanji.hs
@@ -1,3 +1,6 @@
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE RankNTypes #-}
+
 -- |
 -- Module    : Data.Kanji
 -- Copyright : (c) Colin Woodbury, 2015, 2016
@@ -24,15 +27,22 @@
        , isKanjiInLevel
        , levelFromRank
          -- * Analysis
-       , kanjiDensity
-       , elementaryKanjiDensity
        , percentSpread
        , levelDist
        , averageLevel
+       , uniques
+         -- ** Densities
+       , kanjiDensity
+       , elementaryDen
+       , middleDen
+       , highDen
+       , adultDen
        ) where
 
+import           Control.Arrow hiding (second)
 import           Data.Bool (bool)
 import           Data.List (sort, group)
+import qualified Data.Map.Lazy as M
 import qualified Data.Set as S
 import           Lens.Micro
 
@@ -41,7 +51,7 @@
 
 ---
 
--- | All Kanji, grouped by their Level (級) in ascending order.
+-- | All Japanese Kanji, grouped by their Level (級) in ascending order.
 -- Here, ascending order means from the lowest to the highest level,
 -- meaning from 10 to 1.
 allKanji :: [S.Set Kanji]
@@ -59,16 +69,37 @@
 kanjiDensity :: Int -> [Kanji] -> Float
 kanjiDensity len ks = fromIntegral (length ks) / fromIntegral len
 
--- | As above, but only Kanji of the first 1006 are counted (those learned
--- in elementary school in Japan).
-elementaryKanjiDensity :: [Kanji] -> Float
-elementaryKanjiDensity ks = foldl (\acc (_,p) -> acc + p) 0 elementaryQs
-  where elementaryQs  = filter (\(qn,_) -> qn `elem` [Five, Six ..]) dists
-        dists = levelDist ks
+-- | How much of the Kanji found are learned in elementary school in Japan?
+--
+-- > elementaryDen . levelDist :: [Kanji] -> Float
+elementaryDen :: [(Rank,Float)] -> Float
+elementaryDen dists = sum $ dists ^.. each . inRank [Five, Six ..]
 
+-- | How much of the Kanji found are learned by the end of middle school?
+--
+-- > middleDen . levelDist :: [Kanji] -> Float
+middleDen :: [(Rank,Float)] -> Float
+middleDen dists = sum $ dists ^.. each . inRank [Three, Four ..]
+
+-- | How much of the Kanji found are learned by the end of high school?
+--
+-- > highDen . levelDist :: [Kanji] -> Float
+highDen :: [(Rank,Float)] -> Float
+highDen dists = sum $ dists ^.. each . inRank [PreTwo, Three ..]
+
+-- | How much of the Kanji found should be able to read by the average person?
+--
+-- > adultDen . levelDist :: [Kanji] -> Float
+adultDen :: [(Rank,Float)] -> Float
+adultDen dists = sum $ dists ^.. each . inRank [Two, PreTwo ..]
+
+inRank :: [Rank] -> Traversal' (Rank,Float) Float
+inRank rs f (r,n) | r `elem` rs = (r,) <$> f n
+                  | otherwise = pure (r,n)
+
 -- | All `Level`s, with all their `Kanji`, ordered from Level-10 to Level-2.
 levels :: [Level]
-levels = map (uncurry Level) $ zip allKanji [Ten ..]
+levels = zipWith Level allKanji [Ten ..]
 
 -- | What `Level` does a Kanji belong to?
 level :: Kanji -> Maybe Level
@@ -92,7 +123,7 @@
 averageLevel :: [Kanji] -> Float
 averageLevel ks = average ranks
   where ranks = ks ^.. each . to level . _Just . to _rank . to fromRank
-        average ns = (sum ns) / (fromIntegral $ length ns) 
+        average ns = sum ns / fromIntegral (length ns)
 
 -- | How much of each `Level` is represented by a group of Kanji?
 levelDist :: [Kanji] -> [(Rank,Float)]
@@ -111,4 +142,9 @@
 
 -- | Determines how many times each `Kanji` appears in given set of them.
 kanjiQuantities :: [Kanji] -> [(Kanji,Int)]
-kanjiQuantities = map (\ks -> (head ks, length ks)) . group . sort
+kanjiQuantities = map (head &&& length) . group . sort
+
+-- | Which Kanji appeared from each Level in the text?
+uniques :: [Kanji] -> [(Rank,[Kanji])]
+uniques = M.toList . S.foldl h M.empty . S.fromList
+  where h a k = maybe a (\l -> M.insertWith (++) (_rank l) [k] a) $ level k
diff --git a/kanji.cabal b/kanji.cabal
--- a/kanji.cabal
+++ b/kanji.cabal
@@ -1,17 +1,17 @@
 name:                kanji
-version:             2.0.0
+version:             3.0.2
 synopsis:            Perform 漢字検定 (Japan Kanji Aptitude Test) level analysis on Japanese Kanji.
-description:         Perform 漢字検定 (Japan Kanji Aptitude Test) level analysis on Japanese Kanji.        
+description:         Perform 漢字検定 (Japan Kanji Aptitude Test) level analysis on Japanese Kanji.
 license:             GPL-3
 license-file:        LICENSE
 author:              Colin Woodbury
 maintainer:          colingw@gmail.com
 
 homepage:            https://github.com/fosskers/nanq
--- copyright:           
+-- copyright:
 category:            Data
 build-type:          Simple
--- extra-source-files:  
+-- extra-source-files:
 cabal-version:       >=1.10
 
 library
@@ -23,13 +23,13 @@
 
   other-extensions:    FlexibleInstances
 
-  build-depends:       base >= 4.8 && < 4.9
+  build-depends:       base >= 4.8 && < 4.11
                      , containers >= 0.5 && < 0.6
                      , bytestring >= 0.10 && < 0.11
                      , microlens >= 0.4.1 && < 0.5
                      , text >= 1.2 && < 1.3
 
-  -- hs-source-dirs:      
+  -- hs-source-dirs:
   default-language:    Haskell2010
 
   ghc-options: -O2
