This is not about lists vs pd.Series, but rather about the function of parentheses (()) vs brackets ([]) in Python.
Parentheses are used in two main cases: to modify the order of precedence of operations, and to delimit arguments when calling functions.
The difference between 1 + 2 * 3 and (1 + 2) * 3 is obvious, and if you want to pass a and b to a function f, f a b will not work, unlike in, say, Haskell.
We are concerned mostly with the first use here; for example, in this line:
(t.year // 10 * 10).value_counts().sort_index().plot(kind='bar')
Without the parentheses, you would be calling that chain of methods on 10, which wouldn't make sense. Clearly, you want to call them on the result of the parenthesised expression.
Now, in mathematics, brackets can also be used to denote precedence, in conjunction with parentheses, in a case where multiple nested parentheses would be confusing. For example, the two may be equivalent in mathematics:
[(1 + 2) * 3] ** 4
((1 + 2) * 3) ** 4
However, that is not the case in Python: ((1 + 2) * 3) ** 4 can be evaluated, whereas [(1 + 2) * 3] ** 4 is a TypeError, since the part within brackets resolves to a list, and you can't perform exponentiation on lists.
Rather, what happens in something like titles[titles.year >= 1950] is not directly relevant to precedence (though of course anything outside the brackets will not be part of the inner expression).
Instead, the brackets represent indexing; in some way, the value of titles.year >= 1950 is used to get elements from titles (this is done using overloading of the __getitem__ dunder method).
The exact nature of this indexing may differ; lists take integers, dicts take any hashable object and pd.Series take, among other things, boolean pd.Series (that is what is happening here), but they ultimately represent some way to subset the indexed object.
Semantically, therefore, we can see that brackets mean something different from parentheses, and are not interchangeable.
For completeness, using brackets as opposed to parentheses has one tangible benefit: it permits reassignment, because it automatically delegates to either __setitem__ or __getitem__, depending on whether assignment is being performed.
Therefore, you could do something like titles[titles.year >= 1950] = 'Nothing' if you wanted. However, in all cases, titles(titles.year >= 1950) = 'Nothing' delegates to __call__, and therefore will fail in the following way:
SyntaxError: can't assign to function call