RFC switch to Polars as the default dataframe lib in our examples

We now support polars as an output for our transformers and `ColumnTransformer`, but our examples use `pd.DataFrame`.

Our `datasets` module also returnes either a numpy array or a pandas DataFrame.

I'm suggesting that we enable datasets to return a polars dataframe, and to switch our examples from pandas to polars.

This has a few benefits:
- Polars on users' systems takes advantage of multi-core CPUs which is the case for pretty much all users these days. So it's quite faster in most cases.
- Pandas is dealing with issues related to Arrow, and even if they don't require Arrow as a required dependency, there will be behavior changes whether Arrow is installed or not at least on String Dtype.

Another thing is the (in)stability issues related to Pandas API where we need to deal with deprecation warnings very often. Although I'm not sure how stable the API is on the places where we touch the API on the polars side (maybe cc @MarcoGorelli )

WDYT @scikit-learn/core-devs @scikit-learn/documentation-team @scikit-learn/contributor-experience-team 

This is not really a core part of our library, but what we put in our examples and our default choices affect people since many learn from our examples.

COMMENT:
I did an experiment using only Polars (not even Numpy) for my ML pipelines during 2022-2023. I rewrote almost all Transformers and metrics using Polars + some Rust. Let me share my two cents on this issue:

Pros:
(1) You get the speed gain trivially. But this is not the most important factor.
![image](https://github.com/scikit-learn/scikit-learn/assets/72534736/847e61a4-4bf1-4c41-be2e-41228431ecec)

(2) It is so much easier to write for Polars and Polars alone. (Polars in, Polars out)
Why? Because Polars enforces a strict type system. Polars has methods that internally handles null values, which makes Polars so much better than NumPy in practice. Imputation and scaling become trivial. No need to tons of if isinstance(...) and unnecessary branches. Once you start dealing with multiple output types, life gets harder..

(3) A lot more stuff can be written as Polars queries (heck even in SQL queries). ROC AUC can be computed as a SQL query and thus can be translated to Polars, and you can see the speed gain. Target encoding again can be computed as a SQL query and therefore can be rewritten in Polars... Thus using Polars, we can safely say goodbye to category_encoders.

(4) A transformer, in essence, is a function that outputs a recipe on how to replicate a mapping for some columns in the dataframe. As long as this recipe can be pickled, the same transform will work for training and testing, and all data splits. This is where Polars expressions come in. Almost all Polars's native expressions are pickle-able, so they can be reused. (I have not tested Polars plugin expression). This makes feature engineering again trivial in Polars. 

Setting up a custom transfomer is a pain in current Sklearn because function transformers cannot be pickled properly. But say you want to do LogTransform. Do you really need all that inherited class nonsense?  In polars you just do pl.col("a").log() and here you go this is your transformer. We run into this issue a lot in practice, where in our dataset we have feature A and B, but we need to use feature A/B. What we do now is to go back to SQL and add this feature. But during deployment, this adds work for Dev and sometimes the engineered feature gets complicated. Using Polars we can leave the SQL as it is. Pull only the raw feature set and keep feature engineering in Python in a VERY CLEAN way and everything is pickle-able and reusable so life is good. Using Transformer for this is not as clean, and for each project, the reusability of these transformers are suspicious. So I think expressions are superior in this case. We can therefore safely say goodbye to dependencies like Scikit-lego, feature-engine, or whatever feature engineering library that claims to be compatible with sklearn..

Cons:
(1) Backwards compatibility. Obviously a lot of things will need to be rewritten in Polars. This is not a con to me, but is a con to Sklearn the package.
(2) It might be harder to debug when running lazy polars. Because Lazy Polars won't stop at the exact point in the query to tell you what is wrong. Rather, it tells you what is wrong without telling you where exactly is the problem. That said, debugging in current sklearn can also be hard if you are using the BaseTransformers and other low-level classes.

I cannot really think of anything else. 


Other comments:
(1) From my experience, for those who are doing traditional ML, the speed gain pointed out above does not matter so much. It's like 5s to 1s. Nothing critical unless you are serving stuff in real time, which is rare.
(2) I am the author of a package called [polars-ds](https://github.com/abstractqqq/polars_ds_extension). If you are interested in doing data science with Polars and Rust, and like working with dataframe queries, then please check it out!

Labels: RFC