"SELECT LAG(id) FROM cb_cat WHERE id=450";
Window functions work on the result after the WHERE clause is applied. Since id is unique and you ask for exactly one id, that result has a maximum of one row. There just isn't any previous row whatsoever.
Further more relational tables represent relations which are a special form of (multi) sets. And (multi) sets aren't ordered. Unless there's an explicit ORDER BY, the order the tuples of a result are returned in is to be considered random. Window functions depending on an order therefore also need an ORDER BY specified in the OVER clause, unless they just should pick some random row. You didn't specify what defines your order, I assume it's the id in the following.
Using lag() you'd have to select all rows in a derived table where you apply the lag() and then go for the row with the id of 450 in the outer SELECT.
SELECT l
       FROM (SELECT id,
                    lag(id) OVER (ORDER BY id) l
                    FROM cb_cat) x
       WHERE id = 450;

This can be optimized a little. Since id defines the order, we actually only need to look at the records with an id lower than or equal to 450 in the inner SELECT. (I have doubts the optimizer would figure this out by itself. But maybe I'm underestimating its capabilities.)
SELECT l
       FROM (SELECT id,
                    lag(id) OVER (ORDER BY id) l
                    FROM cb_cat
                    WHERE id &lt;= 450) x
       WHERE id = 450;

But if we think of it, what we actually want is the the largest id that is less than 450. lag() isn't the best tool to get this, LIMIT is. We get all ids less than 450, order them in descending order and pick the first one of that ordered result.
SELECT id
       FROM cb_cat
       WHERE id &lt; 450
       ORDER BY id DESC
       LIMIT 1;