Conform Series.to_csv to DataFrame.to_csv
State: closed

(**EDIT**: Most of this comment is related to an old attempt. See [this comment](https://github.com/pandas-dev/pandas/pull/19745#issuecomment-404944973) and below for the content of the current  PR)

I tried my best to keep everything backwards compatible. A simpler solution
would have been simply

```python
def to_csv(self, path_or_buf=None, *args, **kwargs):
    """
    Write Series to a comma-separated values (csv) file

    Parameters
    ----------
    path_or_buf : string or file handle, default None
        File path or object, if None is provided the result is returned as
        a string.
    args
        Positional arguments passed to :func:`pandas.DataFrame.to_csv`
    kwargs
        Keyword arguments passed to :func:`pandas.DataFrame.to_csv`

    Returns
    -------
    None or string
        If `path_or_buf` is None, returns the result as a string, otherwise
        returns None.

    Notes
    -----
    Constructs a DataFrame using :func:`~pandas.Series.to_frame`, then calls
    the method :func:`pandas.DataFrame.to_csv` on that DataFrame.
    """

    df = self.to_frame()
    # result is a string if path_or_buf is None, otherwise None
    result = df.to_csv(path_or_buf, *args, **kwargs)
    return result
```

Unfortunately, such a solution breaks backwards compatibility for the following
reasons:

-   If given as keyword arguments, `path_or_buf` of `DataFrame.to_csv`
    corresponds to the differently named argument `path` of `Series.to_csv`.
-   The ordering of positional arguments for `DataFrame.to_csv` does not agree
    fully with the ordering of positional arguments for `Series.to_csv`
-   The default value of `header` is True for `DataFrame.to_csv` but False for
    `Series.to_csv`

Maybe this is something that could be implemented in the long run?

- [x] closes #19715 
- [ ] tests added / passed
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry

Labels:
name: Output-Formatting
description: __repr__ of pandas objects, to_string
name: API Design
name: IO CSV
description: read_csv, to_csv

Comments:
# [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/19745?src=pr&el=h1) Report
> Merging [#19745](https://codecov.io/gh/pandas-dev/pandas/pull/19745?src=pr&el=desc) into [master](https://codecov.io/gh/pandas-dev/pandas/commit/610a19a333c40ff67b8feb5f96c29cfae8495a3e?src=pr&el=desc) will **increase** coverage by `<.01%`.
> The diff coverage is `96.55%`.

[![Impacted file tree graph](https://codecov.io/gh/pandas-dev/pandas/pull/19745/graphs/tree.svg?src=pr&token=eZ4WkYLtcO&width=650&height=150)](https://codecov.io/gh/pandas-dev/pandas/pull/19745?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master   #19745      +/-   ##
==========================================
+ Coverage   91.95%   91.95%   +<.01%     
==========================================
  Files         166      166              
  Lines       50274    50290      +16     
==========================================
+ Hits        46231    46246      +15     
- Misses       4043     4044       +1
```

| Flag | Coverage Δ | |
|---|---|---|
| #multiple | `90.35% <96.55%> (ø)` | :arrow_up: |
| #single | `42.17% <55.17%> (ø)` | :arrow_up: |

| [Impacted Files](https://codecov.io/gh/pandas-dev/pandas/pull/19745?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pandas/core/frame.py](https://codecov.io/gh/pandas-dev/pandas/pull/19745/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZnJhbWUucHk=) | `97.18% <ø> (-0.02%)` | :arrow_down: |
| [pandas/core/generic.py](https://codecov.io/gh/pandas-dev/pandas/pull/19745/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZ2VuZXJpYy5weQ==) | `96.47% <100%> (+0.01%)` | :arrow_up: |
| [pandas/core/series.py](https://codecov.io/gh/pandas-dev/pandas/pull/19745/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvc2VyaWVzLnB5) | `94.09% <94.44%> (-0.02%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/19745?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/19745?src=pr&el=footer). Last update [610a19a...22a3dca](https://codecov.io/gh/pandas-dev/pandas/pull/19745?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

I realised that there's a decorator for deprecating keyword arguments and did my best to use that instead. I also tried to add versionadded and deprecated directives to the docstring by mimicking the way they are used in `pandas.read_excel`.
xref #18958
ideally we would consolidate the tests with DataFrame.to_csv to ensure coverage of all of the kwargs. could be in this PR or followon
@dahlbaek can you or @pandas-dev/pandas-core have a look here
@jreback @jorisvandenbossche : Maybe a silly question, but couldn't we just implement `to_csv` in `NDFrame`, deprecate `Series.to_csv` entirely and then delete the method later on?  This would ensure conformity for `DataFrame` and `Series` (in the future) and avoid this whole mess of keyword deprecations.
@gfyoung 

yes
Alrighty then.  I'll create a new PR to supersede this one.
> deprecate Series.to_csv entirely and then delete the method later on?

... but wouldn't the deprecation message look like "``Series.to_csv`` is being deprecated, please use ``Series.to_csv``"?! I mean: from the user point of view, wouldn't it just be a deprecation of _arguments_?

(and in that case, wouldn't we better immediately apply the solution [above](https://github.com/pandas-dev/pandas/pull/19745#issue-169809576), with deprecation warnings for the three problematic keywords?)
I've been meaning to give this a second go for a while, but haven't had time. Clearly, I'm not as experienced as others in this thread regarding the pandas codebase, but maybe someone will find some of my thoughts interesting anyway:

- If the keywords `path` and `path_or_buf` are going to be aligned, perhaps they should both be changed to `filepath_or_buffer` in order to also align with the `read_csv` method.
- I feel like the cleanest solution would be to
  1) Move the `DataFrame` method to `NDFrame` as-is. This would be backwards compatible, so you do not need any deprecation warnings (or, if `path_or_buf` is changed to `filepath_or_buffer`, the current keyword deprecation decorator could be applied to the new method on `NDFrame`).
  2) Add general purpose decorator(s) to enable deprecation of the order of positional arguments and default value of a keyword argument. The deprecation warning for ordering would trigger if you are using "enough" positional arguments for the reordering to have any effect, and the default value warning would trigger if you are not explicitly assigning a value to the affected keyword argument. The decorators might prove useful in other situations.
  3) Implement required changes on `Series` in order to align with the `NDFrame` method, using the new deprecation decorators.
  4) Once the deprecation period is over (so that warnings are no longer displayed, but the `Series` method is fully aligned with the `NDFrame` method), move the `Series` method to `NDFrame`, which would be an internal change that users would not need to know about.
- Alternatively, I feel the quickest solution would be moving everything to `NDFrame` in a single breaking change with a nice whatsnew message and get it over with. However, saving stuff to csv seems like a pretty core functionality — keeping in mind that quite a few users of pandas probably never read the whatsnew or even keep track of which version of pandas they are using, I feel like some kind of deprecation warning is in order.

If a decision is made as to how to proceed, I would be happy to lend a hand. Unfortunately, I do not currently have time to go through all the steps for a "clean" solution on my own.

EDIT: Reading through my "clean" solution, I have to admit it does not look too clean. Here is an alternative: Move everything to `NDFrame`, but for a limited time, use the `path` and `path_or_buf` keywords to trigger deprecation warnings. Basically, to align with `read_csv` those arguments should be changed to `filepath_or_buffer`, so any user who is supplying `path`, `path_or_buf` or relying on the position of that argument would be suspected of relying on the old method signatures. This way, there will be false positives, but I think that is better than having code break silently.
@dahlbaek : Your point regarding `path_or_buf` is well-taken.  I would do that however after migrating to `NDFrame` (in the interest of modularity and fewer moving parts).
Hello @dahlbaek! Thanks for updating the PR.







Cheers ! There are no PEP8 issues in this Pull Request. :beers: 

##### Comment last updated on July 13, 2018 at 22:24 Hours UTC
So, I figured I would do a proof of concept with a decorator-based approach. If anyone finds this appropriate, I will fill in tests based on the work by @gfyoung in #21868.

Pros:
- Passing all arguments through a decorator seems like the natural thing to do — this way full information is available for inspection, making the deprecation warnings easy to write.
- The signature of `Series.to_csv` stays intact and honest.
- Deprecation is all contained in a single separate decorator (arguably not important, since `Series.to_csv` would live on only for the deprecation period).

Cons:
- The decorator adds an extra level of indirection.
- Users that insist on passing multiple positional arguments (instead of using keyword arguments) will have to live with the deprecation warning throughout the deprecation period.
- Users that use positional arguments (and who are OK with the warning) cannot switch to the new ordering until the end of the deprecation period — and then they must do so immediately when upgrading.

(**EDIT**: Changed pros and cons according to comments by @toobaz)
> Deprecation is all contained in a single separate decorator.

As correctly [pointed out](https://github.com/pandas-dev/pandas/pull/21868#issuecomment-404779517) by @gfyoung , this is already true for ``Series.to_csv`` itself.
> Users that insist on passing multiple positional arguments (in favor of using keyword arguments) will have to live with the deprecation warning throughout the deprecation period.

This is a consequence of your specific implementation, not of the fact that it's contained in a decorator.
> As correctly pointed out by @gfyoung , this is already true for Series.to_csv itself.

No, that comment has to do with what should happen at the end of the deprecation period. The way I implemented things here you would not be able to simply remove the decorator, because I want to keep the signature of `to_csv` intact and honest. The point I want to make is different: This way, all of the code related to deprecation is contained in a shell surrounding `to_csv` instead of being cooked into `to_csv`. It's a matter of code separation.

> This is a consequence of your specific implementation, not of the fact that it's contained in a decorator.

That is correct.
> It's a matter of code separation.

``Series.to_csv`` makes sense only in light of the deprecation process, there is no "real" code to separate. Anyway, we are used to deprecations managed through decorators, so the second of the "Pros" still holds.

Among the "Cons" we should add "users who use positional arguments [and who are OK with the warning] cannot switch to the new ordering until the end of the deprecation period - and then they must immediately when upgrading", right? I consider this more relevant than the warning. (Compare with #21896 )
Agreed and added. This solution is not friendly towards users that want to pass positional arguments (other than `path_or_buf`).

One possibility would be to combine the decorator approach with the approach of #21896. That way, one could have both the intact signature and a solution which works for users that like to use positional arguments.
We're pushing to merge #21896 over this one.