diff --git a/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,81 @@
+{- rank-product
+Gregory W. Schwartz
+
+Get the rank product of a data frame.
+-}
+
+{-# LANGUAGE DataKinds         #-}
+{-# LANGUAGE DeriveGeneric     #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeOperators     #-}
+
+module Main where
+
+-- Remote
+import Data.Char (ord)
+import Data.Maybe (fromMaybe)
+import Options.Generic
+import qualified Control.Lens as L
+import qualified Data.ByteString.Lazy.Char8 as B
+import qualified Data.Csv as CSV
+
+-- Local
+import Statistics.Types
+import Statistics.Load
+import Statistics.RankProduct
+
+-- | Command line arguments
+data Options = Options { permutations :: Maybe Int
+                                     <?> "([1000] | INT) Number of permutations for rank product p-value."
+                       , delimiter    :: Maybe Char
+                                     <?> "([,] | CHAR) The delimiter of the CSV file. Format is name,replicate,value."
+                       , sortType         :: Maybe Sort
+                                     <?> "([Ascending] | Descending) How to sort for output."
+                       }
+               deriving (Generic)
+
+modifiers :: Modifiers
+modifiers = lispCaseModifiers { shortNameModifier = short }
+  where
+    short x         = firstLetter x
+
+instance ParseField Sort
+
+instance ParseRecord Options where
+    parseRecord = parseRecordWithModifiers modifiers
+
+main :: IO ()
+main = do
+    opts <- getRecord "rank-product, Gregory W. Schwartz.\
+                      \ Compute the rank product for a data frame.\
+                      \ Format is name,replicate,value."
+
+    let permutations' =
+          Permutations . fromMaybe 1000 . unHelpful . permutations $ opts
+        delim'        =
+            Delimiter . fromMaybe ',' . unHelpful . delimiter $ opts
+        sortType'     = fromMaybe Ascending . unHelpful . sortType $ opts
+        decodeOpt       = CSV.defaultDecodeOptions
+                            { CSV.decDelimiter =
+                                fromIntegral (ord . unDelimiter $ delim')
+                            }
+        encodeOpt       = CSV.defaultEncodeOptions
+                            { CSV.encDelimiter =
+                                fromIntegral (ord . unDelimiter $ delim')
+                            }
+
+    entities <- loadNamedEntities
+              . either error snd
+              . CSV.decodeByNameWith decodeOpt
+            <$> B.getContents
+
+    rankedEntities <-
+      namedRankProductPermutation permutations' sortType' entities
+
+    let header = "name,rank,pvalue\n"
+        body = L.over L._3 unPValue
+             . L.over L._2 unRankProductEntity
+             . L.over L._1 unName
+           <$> rankedEntities
+
+    B.putStrLn . (header <>) $ CSV.encodeWith encodeOpt body
diff --git a/rank-product.cabal b/rank-product.cabal
--- a/rank-product.cabal
+++ b/rank-product.cabal
@@ -1,5 +1,5 @@
 name:                rank-product
-version:             0.2.0.1
+version:             0.2.1.0
 synopsis:            Find the rank product of a data set.
 description:         Find the rank product of a data set and get the p-value from a permutation test.
 homepage:            http://github.com/GregorySchwartz/rank-product#readme
@@ -16,10 +16,28 @@
 library
   hs-source-dirs:      src
   exposed-modules:     Statistics.Types
+                     , Statistics.Load
                      , Statistics.RankProduct
   build-depends:       base >= 4.7 && < 5
+                     , containers
                      , random-fu
+                     , text
+                     , vector
   ghc-options:         -O2
+  default-language:    Haskell2010
+
+executable rank-product
+  hs-source-dirs:      app
+  main-is:             Main.hs
+  ghc-options:         -threaded -rtsopts -O2
+  build-depends:       base
+                     , optparse-generic
+                     , rank-product
+                     , bytestring
+                     , cassava
+                     , containers
+                     , lens
+                     , vector
   default-language:    Haskell2010
 
 source-repository head
diff --git a/src/Statistics/Load.hs b/src/Statistics/Load.hs
new file mode 100644
--- /dev/null
+++ b/src/Statistics/Load.hs
@@ -0,0 +1,43 @@
+{- Load
+Gregory W. Schwartz
+
+Collects the functions pertaining to loading the data frame from rows of a file.
+-}
+
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Statistics.Load
+    ( loadNamedEntities
+    ) where
+
+-- Standard
+import Data.List (sort)
+import qualified Data.Foldable as F
+import qualified Data.Map.Strict as Map
+import qualified Data.Sequence as Seq
+import qualified Data.Text as T
+import qualified Data.Text.Read as T
+import qualified Data.Vector as V
+
+-- Cabal
+
+-- Local
+import Statistics.Types
+
+-- | Load entities from a list of rows.
+loadNamedEntities :: V.Vector (Map.Map T.Text T.Text) -> [NamedEntity]
+loadNamedEntities = fmap (uncurry NamedEntity)
+                  . Map.toAscList
+                  . Map.map (fmap snd . sort . F.toList)
+                  . Map.fromListWith (Seq.><)
+                  . fmap lookupInfo
+                  . V.toList
+  where
+    lookupErr x = Map.findWithDefault (error $ "Missing column: " <> show x) x
+    lookupInfo m =
+      ( Name $ lookupErr "name" m
+      , Seq.singleton ( lookupErr "replicate" m
+                      , either error fst . T.double $ lookupErr "value" m
+                      )
+      )
diff --git a/src/Statistics/RankProduct.hs b/src/Statistics/RankProduct.hs
--- a/src/Statistics/RankProduct.hs
+++ b/src/Statistics/RankProduct.hs
@@ -12,6 +12,7 @@
     , rankProduct
     , prerankProduct
     , rankProductPermutation
+    , namedRankProductPermutation
     ) where
 
 -- Standard
@@ -19,7 +20,6 @@
 import Data.List
 import Data.Random
 
-
 -- Cabal
 
 -- Local
@@ -112,3 +112,20 @@
             fmap (\e -> PValue $ (fromIntegral e) / (fromIntegral permutations)) exp
 
     return . zip obs $ pVals
+
+-- | Get the rank product of a list of NamedEntity as well as the permutation
+-- p-value, removing entities without all replicates. Ascending ranks the lowest
+-- value as 1 while Descending ranks the highest value as 1.
+namedRankProductPermutation :: Permutations
+                            -> Sort
+                            -> [NamedEntity]
+                            -> IO [(Name, RankProductEntity, PValue)]
+namedRankProductPermutation permutations sortType entities =
+  fmap (zipWith (\ !n (!r, !p) -> (n, r, p)) names)
+    . rankProductPermutation permutations sortType
+    . fmap (\(NamedEntity _ xs) -> Entity xs)
+    . filter ((== maxReplicates) . length . values)
+    $ entities
+  where
+    names = fmap name entities
+    maxReplicates = maximum . fmap (length . values) $ entities
diff --git a/src/Statistics/Types.hs b/src/Statistics/Types.hs
--- a/src/Statistics/Types.hs
+++ b/src/Statistics/Types.hs
@@ -4,9 +4,12 @@
 Collects the types used in the program
 -}
 
+{-# LANGUAGE StrictData #-}
+
 module Statistics.Types where
 
 -- Standard
+import qualified Data.Text as T
 
 -- Cabal
 
@@ -14,7 +17,9 @@
 
 
 -- Basic
-newtype Permutations      = Permutations Int
+newtype Delimiter         = Delimiter { unDelimiter :: Char } deriving (Read, Show)
+newtype Name              = Name { unName :: T.Text } deriving (Eq, Ord, Show)
+newtype Permutations      = Permutations Int deriving (Show)
 newtype Entity            = Entity { unEntity :: [Double] } deriving (Show)
 newtype RankEntity        = RankEntity { unRankEntity :: [Double] } deriving (Show)
 newtype RankProductEntity = RankProductEntity
@@ -24,3 +29,8 @@
 
 -- Advanced
 data Sort = Ascending | Descending deriving (Eq, Ord, Read, Show)
+
+data NamedEntity = NamedEntity { name :: Name
+                               , values :: [Double]
+                               }
+                   deriving (Show)
