Also consider that using apply() with a function is typically quite inefficient. Try to use vectorized operations whenever you can...
This is a more efficient expression to normalize each column according to the minimum and maximum for that column:
min = df.min() # per column
max = df.max() # per column
df.join(np.round((df - min) / (max - min), 2).add_prefix('Norm_'))
That's much faster than using apply() on a function. For your sample DataFrame:
%timeit df.join(np.round((df - df.min()) / (df.max() - df.min()), 2).add_prefix('Norm_'))
9.89 ms ± 102 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
While the version with apply takes about 4x longer:
%timeit df.join(df.apply(func).add_prefix('Norm_'))
45.8 ms ± 1.16 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
But this difference grows quickly with the size of the DataFrame. For example, with a DataFrame with size 1,000 x 26, I get
37.2 ms ± 269 µs for the version using vectorized instructions, versus 19.5 s ± 1.82 s for the version using apply, around 500x faster!