diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Revision history for dataframe
 
+## 1.1.2.1
+* Add `over` and `median` functions to typed API
+* Fix bug opening web plots in MacOS
+
 ## 1.1.2.0
 * Safe read can now choose between Either and Maybe for error handling.
 * Add countAll and over (window) functions.
diff --git a/dataframe.cabal b/dataframe.cabal
--- a/dataframe.cabal
+++ b/dataframe.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.4
 name:               dataframe
-version:            1.1.2.0
+version:            1.1.2.1
 
 synopsis: A fast, safe, and intuitive DataFrame library.
 
diff --git a/src/DataFrame/Display/Web/Plot.hs b/src/DataFrame/Display/Web/Plot.hs
--- a/src/DataFrame/Display/Web/Plot.hs
+++ b/src/DataFrame/Display/Web/Plot.hs
@@ -1062,9 +1062,10 @@
     putStr "Saving plot to: "
     putStrLn fullPath
     T.writeFile fullPath p
-    if operatingSystem == "mingw32"
-        then openFileSilently "start" fullPath
-        else openFileSilently "xdg-open" fullPath
+    case operatingSystem of
+        "mingw32" -> openFileSilently "start" fullPath
+        "darwin" -> openFileSilently "open" fullPath
+        _ -> openFileSilently "xdg-open" fullPath
     pure ()
 
 openFileSilently :: FilePath -> FilePath -> IO ()
diff --git a/src/DataFrame/Internal/Interpreter.hs b/src/DataFrame/Internal/Interpreter.hs
--- a/src/DataFrame/Internal/Interpreter.hs
+++ b/src/DataFrame/Internal/Interpreter.hs
@@ -283,6 +283,11 @@
     -}
     Group :: (Columnable a) => V.Vector Column -> Value a
 
+instance (Show a) => Show (Value a) where
+    show (Scalar v) = show v
+    show (Flat v) = show v
+    show (Group v) = show v
+
 -- | The interpretation context.
 data Ctx
     = FlatCtx DataFrame
@@ -622,7 +627,9 @@
 parseWith :: (Read a) => (Either String a -> b) -> String -> b
 parseWith f s = case reads s of
     [(x, "")] -> f (Right x)
-    _ -> f (Left s)
+    _ -> case reads (show s) of
+        [(x, "")] -> f (Right x)
+        _ -> f (Left s)
 
 tryParseWith ::
     forall a b.
@@ -656,7 +663,18 @@
                                         )
                                         v
                     Nothing -> castMismatch @c @b
-    UnboxedColumn _ (_ :: VU.Vector c) -> castMismatch @c @b
+    UnboxedColumn bm (v :: VU.Vector c) -> case bm of
+        Nothing -> Right $ fromVector @b $ V.map (parseWith onResult . show) (V.convert v)
+        Just bitmap ->
+            Right $
+                fromVector @b $
+                    V.imap
+                        ( \i x ->
+                            if bitmapTestBit bitmap i
+                                then parseWith onResult (show x)
+                                else onResult (Left "null")
+                        )
+                        (V.convert v)
 
 {- | When the output type @b@ is @Maybe c@ (or @Maybe (Maybe c)@) and the
 column stores plain @c@ values, wrap each element in 'Just'.
diff --git a/src/DataFrame/Typed.hs b/src/DataFrame/Typed.hs
--- a/src/DataFrame/Typed.hs
+++ b/src/DataFrame/Typed.hs
@@ -108,6 +108,14 @@
     DataFrame.Typed.Expr.minimum,
     DataFrame.Typed.Expr.maximum,
     collect,
+    over,
+
+    -- * Cast / coercion expressions
+    castExpr,
+    castExprWithDefault,
+    castExprEither,
+    unsafeCastExpr,
+    toDouble,
 
     -- * Typed sort orders
     TSortOrder (..),
diff --git a/src/DataFrame/Typed/Expr.hs b/src/DataFrame/Typed/Expr.hs
--- a/src/DataFrame/Typed/Expr.hs
+++ b/src/DataFrame/Typed/Expr.hs
@@ -104,17 +104,20 @@
     -- * Aggregation combinators
     sum,
     mean,
+    median,
     count,
     countAll,
     minimum,
     maximum,
     collect,
+    over,
 
     -- * Cast / coercion expressions
     castExpr,
     castExprWithDefault,
     castExprEither,
     unsafeCastExpr,
+    toDouble,
 
     -- * Named expression helper
     as,
@@ -156,7 +159,14 @@
  )
 import DataFrame.Internal.Types (Promote, PromoteDiv)
 
-import DataFrame.Typed.Schema (AssertPresent, SafeLookup)
+import qualified Data.Vector.Unboxed as VU
+import DataFrame.Typed.Schema (
+    AllKnownSymbol,
+    AssertAllPresent,
+    AssertPresent,
+    SafeLookup,
+    symbolVals,
+ )
 import DataFrame.Typed.Types (TExpr (..), TSortOrder (..))
 import Prelude hiding (maximum, minimum, sum)
 
@@ -528,6 +538,10 @@
 mean :: (Columnable a, Real a) => TExpr cols a -> TExpr cols Double
 mean (TExpr e) = TExpr (F.mean e)
 
+median ::
+    (Columnable a, Real a, VU.Unbox a) => TExpr cols a -> TExpr cols Double
+median (TExpr e) = TExpr (F.median e)
+
 count :: (Columnable a) => TExpr cols a -> TExpr cols Int
 count (TExpr e) = TExpr (F.count e)
 
@@ -544,6 +558,12 @@
 collect :: (Columnable a) => TExpr cols a -> TExpr cols [a]
 collect (TExpr e) = TExpr (F.collect e)
 
+over ::
+    forall (names :: [Symbol]) cols a.
+    (Columnable a, AllKnownSymbol names, AssertAllPresent names cols) =>
+    TExpr cols a -> TExpr cols a
+over (TExpr e) = TExpr{unTExpr = F.over (symbolVals @names) e}
+
 -------------------------------------------------------------------------------
 -- Cast / coercion expressions
 -------------------------------------------------------------------------------
@@ -588,6 +608,9 @@
             (fromRight (error "unsafeCastExpr: unexpected Nothing in column"))
             e
         )
+
+toDouble :: (Columnable a, Real a) => TExpr cols a -> TExpr cols Double
+toDouble (TExpr e) = TExpr (F.toDouble e)
 
 -------------------------------------------------------------------------------
 -- Named expression helper
diff --git a/tests/Operations/Window.hs b/tests/Operations/Window.hs
--- a/tests/Operations/Window.hs
+++ b/tests/Operations/Window.hs
@@ -111,7 +111,7 @@
     expectedMeans = [15.0, 15.0, 60.0, 60.0, 60.0] :: [Double]
     actualMeans = case DI.getColumn "group_mean" result of
         Nothing -> error "group_mean column not found"
-        Just col -> DI.toList @Double col
+        Just c -> DI.toList @Double c
 
 overSumTest :: Test
 overSumTest =
@@ -134,7 +134,7 @@
     expectedSums = [30, 30, 300, 300] :: [Int]
     actualSums = case DI.getColumn "group_sum" result of
         Nothing -> error "group_sum column not found"
-        Just col -> DI.toList @Int col
+        Just c -> DI.toList @Int c
 
 overCountTest :: Test
 overCountTest =
@@ -157,7 +157,7 @@
     expectedCounts = [3, 3, 3, 2, 2] :: [Int]
     actualCounts = case DI.getColumn "group_count" result of
         Nothing -> error "group_count column not found"
-        Just col -> DI.toList @Int col
+        Just c -> DI.toList @Int c
 
 mixedGlobalAndOverTest :: Test
 mixedGlobalAndOverTest =
@@ -182,7 +182,7 @@
     expectedDeviations = [-5.0, 5.0, -50.0, 50.0] :: [Double]
     actualDeviations = case DI.getColumn "deviation" result of
         Nothing -> error "deviation column not found"
-        Just col -> DI.toList @Double col
+        Just c -> DI.toList @Double c
 
 -- | Example from https://www.sumsar.net/blog/pandas-feels-clunky-when-coming-from-r/
 blogPostExampleGlobal :: Test
