Wordpress - Show related posts by category but ignore one category

What I could gather from your question is:

  1. You want to ignore one category (may be more) in the related post query.

  2. However, you don't want to actually exclude the posts from that category (in case any post belongs to that category, but also belongs to another category you want to look for).

Based on the assumption above, you may use the following CODE (some explanation is given within the CODE in comments):

    // set the category ID (or multiple category IDs)
    // you want to ignore in the following array
    $cats_to_ignore = array( 2 );
    $categories = wp_get_post_categories( get_the_ID() );
    $category_in = array_diff( $categories, $cats_to_ignore );
    // ignore only if we have any category left after ignoring
    if( count( $category_in ) == 0 ) {
        $category_in = $categories;
    }
    $cat_args = array(
        'category__in'   => $category_in,
        'posts_per_page' => 4,
        'orderby'        => 'date',
        'post__not_in'   => array( get_the_ID() )
        );
    $cat_query = new WP_Query( $cat_args );
    while ( $cat_query->have_posts() ) : $cat_query->the_post();
        /* just example markup for related posts */
        echo '<h2><a href="' . get_the_permalink() . '">' . get_the_title() . '</a></h2>';
    endwhile;
    // reset $post after custom loop ends (if you need the main loop after this point)
    wp_reset_postdata();

You cannot use 'cat' => '-2' or 'category__not_in' => array(2) because that will exclude all posts having category 2, even if those posts have other categories as well. So, instead of excluding, I've ignored the category 2 in the query with this CODE: array_diff( $categories, $cats_to_ignore );.

Note: I've used WP_Query instead of get_posts() because iteration with WP_Query looks more like the original loop. But of course you can use the get_posts() function as well, as it calls WP_Query internally anyway.