In the bash shell, subshells are implemented by forking a child process, so you won't see a case of a subshell not running in a child process in that shell.
ksh93 is the only shell that I know that skips the forking when possible for subshells (an optimisation that is still quite buggy and that the successive people that have tried to maintain it after AT&T disbanded the team that had written it have considered removing).
Some shells like FreeBSD's sh can skip the fork in very specific cases, like in:
var=$(printf %04d "$n")
(here with a printf builtin, and no change to the environment is being done in there).
In a pipeline, all components have to run concurrently, so they have to run in separate processes, even in ksh93.
In bash, they all run in child processes. In AT&T ksh or zsh, or with bash -O lastpipe (when non-interactive), the rightmost one doesn't (of course, you still need to fork a child process to run external commands such as ps).
You don't see an extra bash process in ps >&2 | ps or (ps) because ps is executed directly in that child process (which, which before executing ps was bash interpreting the pipeline component: the subshell. For instance, in:
n=0; /bin/true "$((n=1))" | /bin/echo "$((n=2))"; echo "$$"
You'll see 2 and 0 in bash, and 2 and 2 in zsh/ksh93. /bin/true and /bin/echo are executed in child processes, /bin/true directly in the subshell process that had done n=1 earlier, same in bash for /bin/echo (and n=2), but in zsh/ksh/bash -O lastpipe, the n=2 was done in the main shell process, and a child only forked to execute that external utility, just like when you run /bin/echo "$((n=2))" not as part of a pipeline.
In bash (contrary to zsh/ksh), you do see an extra bash process in (: anything; ps), the optimisation is only done if the subshell has only one external command, you'd need to use exec to do that optimisation by hand there: (: anything; exec ps).
Same goes for { ps; } | cat.