PERF: always slice when indexing on columns
State: closed

- [ ] closes #xxxx
- [x] tests added / passed
- [x] passes `black pandas`
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [ ] whatsnew entry

Touched on in #32779, this makes all\* DataFrame indexing-on-columns do slicing instead of taking, so doesn't make copies.

\* Actually there is a different path in `_slice_take_blocks_ax0` that we go down if we `self._is_single_block`, can update that later if we decide this is something we want to do.

```
In [3]: dti = pd.date_range("2016-01-01", periods=10**5, freq="S")                                                                                                                                                  
In [4]: df = pd.DataFrame._from_arrays([dti]*10 + [dti - dti] * 10 + [dti.to_period("D")]*10, columns=range(30), index=range(len(dti)))                                                                             

In [8]: arr = np.arange(30)                                                                                                                                                                                         
In [9]: np.random.shuffle(arr)                                                                                                                                                                                      

In [10]: %timeit df[arr]
8.35 ms ± 64.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)    # <-- master                                                                                                                                                     
650 µs ± 52.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)   # <-- PR
```

The tradeoff is that we end up with less-consolidated results.  I'm OK with that, but there may be downsides I'm not aware of, will wait for others to weigh in.

Labels:
name: Indexing
description: Related to indexing on series/frames, not to indexes themselves
name: Performance
description: Memory or execution speed performance
name: Internals
description: Related to non-user accessible pandas implementation
name: Mothballed
description: Temporarily-closed PR the author plans to return to
name: Copy / view semantics

Comments:
```
asv continuous -E virtualenv -f 1.1 master HEAD -b join_merge
[...]
       before           after         ratio
     [3d4f9dc1]       [10f63497]
     <master>         <perf-never-take>
-     12.7±0.09ms       11.3±0.2ms     0.89  join_merge.MergeAsof.time_on_uint64('forward', None)
-     15.2±0.06ms      13.6±0.03ms     0.89  join_merge.MergeAsof.time_on_int32('nearest', 5)
-     15.0±0.08ms       13.3±0.1ms     0.89  join_merge.MergeAsof.time_on_int32('nearest', None)
-     14.7±0.09ms       13.0±0.1ms     0.88  join_merge.MergeAsof.time_on_int('nearest', None)
-      13.0±0.1ms       11.5±0.2ms     0.88  join_merge.MergeAsof.time_on_int32('forward', None)
-     13.1±0.04ms      11.5±0.09ms     0.88  join_merge.MergeAsof.time_on_int32('forward', 5)
-     15.1±0.09ms      13.2±0.05ms     0.88  join_merge.MergeAsof.time_on_int('nearest', 5)
-     15.1±0.07ms       13.2±0.2ms     0.88  join_merge.MergeAsof.time_on_uint64('nearest', 5)
-     12.4±0.08ms       10.8±0.3ms     0.87  join_merge.MergeAsof.time_on_uint64('backward', 5)
-     12.7±0.07ms      11.0±0.05ms     0.87  join_merge.MergeAsof.time_on_int32('backward', 5)
-     12.9±0.09ms      11.2±0.02ms     0.87  join_merge.MergeAsof.time_on_uint64('forward', 5)
-     12.4±0.06ms      10.8±0.07ms     0.87  join_merge.MergeAsof.time_on_int32('backward', None)
-     12.7±0.05ms       11.0±0.4ms     0.87  join_merge.MergeAsof.time_on_int('forward', 5)
-     12.7±0.05ms       10.9±0.1ms     0.86  join_merge.MergeAsof.time_on_int('forward', None)
-     12.3±0.06ms      10.5±0.06ms     0.86  join_merge.MergeAsof.time_on_uint64('backward', None)
-     12.2±0.08ms      10.5±0.08ms     0.86  join_merge.MergeAsof.time_on_int('backward', 5)
-      61.9±0.9ms       35.1±0.7ms     0.57  join_merge.Align.time_series_align_left_monotonic

SOME BENCHMARKS HAVE CHANGED SIGNIFICANTLY.
PERFORMANCE INCREASED.

```
In general for indexing, indexing with an array typically means "fancy indexing" and can be expected to return a copy and not a view (like a slice does). 

Now, for *selecting columns* we don't always follow the standard indexing rules so this might be less applicable, and I am not sure if we currently make any guarantees regarding this  (as I would also assume it might different between consolidated blocks and EA columns). 
But changing copy to view *is* changing semantics for the user, and this might lead to unexpected results/bugs. So something we need to consider are the user facing changes.
> In general for indexing, indexing with an array typically means "fancy indexing" and can be expected to return a copy and not a view (like a slice does).

Figuring out the intended/desired/promised behavior seems important.  cc @pandas-dev/pandas-core do you expect the following to return views on the underlying data or copies (or mixed, or don't care, or ...)

```
df[array_of_labels]
df[list_of_labels]
df._slice(slice_obj, axis=1)
df.loc[:, array_of_labels]
df.loc[:, list_of_labels]
df.loc[:, boolean_mask]
df.iloc[:, array_of_positions]
df.iloc[:, list_of_positions]
df.iloc[:, boolean_mask]
df.iloc[:, slice_obj]
```

or any other variants that may be relevant?
I think "view whenever possible" is what we should strive for. Are we able to do that consistently for each of those cases, or does it depend on the block layout of the DataFrame?

I understand that it might have user-facing changes, but I don't believe that we document the current behavior anywhere.
@jbrockmendel can you maybe open an issue for this? As your questions seems to be more broad than the actual PR, and to not mix that discussion with code review of the actual PR
i cant reproduce the test failures locally
Opened #33780, mothballing this.
this is quite old, happen to reopen if actively worked on.