Repository: pandas-dev/pandas

Issue:
can't pass '%' to pandas.DataFrame.add_prefix
#### Code Sample, a copy-pastable example if possible
From this stackoverflow [question](https://stackoverflow.com/q/45453508/2336654)

```python
import pandas as pd
df = pd.DataFrame(dict(A=[1]))
df.add_prefix('%')
```
#### Problem description
Current implementation uses old style string formatting and breaks when passing a percent sign

#### Expected Output

```
   %A
0   1
```
#### Output of ``pd.show_versions()``

<details>

[paste the output of ``pd.show_versions()`` here below this line]
INSTALLED VERSIONS
------------------
commit: None
python: 3.6.0.final.0
python-bits: 64
OS: Darwin
OS-release: 16.6.0
machine: x86_64
processor: i386
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: en_US.UTF-8

pandas: 0.20.3
pytest: 3.0.5
pip: 9.0.1
setuptools: 36.2.0
Cython: 0.25.2
numpy: 1.13.1
scipy: 0.18.1
xarray: 0.9.5
IPython: 5.1.0
sphinx: 1.5.1
patsy: 0.4.1
dateutil: 2.6.1
pytz: 2017.2
blosc: None
bottleneck: 1.2.0
tables: 3.3.0
numexpr: 2.6.1
feather: 0.4.0
matplotlib: 2.0.0
openpyxl: 2.4.1
xlrd: 1.0.0
xlwt: 1.2.0
xlsxwriter: 0.9.6
lxml: 3.7.2
bs4: 4.5.3
html5lib: 0.9999999
sqlalchemy: 1.1.5
pymysql: None
psycopg2: None
jinja2: 2.9.4
s3fs: None
pandas_gbq: None
pandas_datareader: 0.4.0

</details>



Discussion:
@pirsquared : Thanks for the issue!  This seems a little unusual to me.  Has this behavior ever worked for you (or anyone) in the past?
I suspect it never has. But it seems as though it should.
On Wed, Aug 2, 2017 at 12:50 AM gfyoung <notifications@github.com> wrote:

> @pirsquared <https://github.com/pirsquared> : Thanks for the issue! This
> seems a little unusual to me. Has this behavior ever worked for you (or
> anyone) in the past?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/pandas-dev/pandas/issues/17151#issuecomment-319596173>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/ABz_el5NcPWXut7SncLhfY6ywFhb2XmKks5sUCpXgaJpZM4Oqte->
> .
>

I can do this.  Should be pretty straightforward to switch the `add_prefix` and `add_suffix` methods from using old style string formatting to `.format`, which should handle strings like '%'.
@jschendel : Go for it!  PR to add this functionality is welcome.

Resolving patch:
diff --git a/pandas/core/internals.py b/pandas/core/internals.py
--- a/pandas/core/internals.py
+++ b/pandas/core/internals.py
@@ -5,6 +5,7 @@
 import operator
 from datetime import datetime, timedelta, date
 from collections import defaultdict
+from functools import partial
 
 import numpy as np
 
@@ -2959,11 +2960,11 @@ def rename_axis(self, mapper, axis, copy=True, level=None):
         return obj
 
     def add_prefix(self, prefix):
-        f = (str(prefix) + '%s').__mod__
+        f = partial('{prefix}{}'.format, prefix=prefix)
         return self.rename_axis(f, axis=0)
 
     def add_suffix(self, suffix):
-        f = ('%s' + str(suffix)).__mod__
+        f = partial('{}{suffix}'.format, suffix=suffix)
         return self.rename_axis(f, axis=0)
 
     @property


Test patch:
diff --git a/pandas/tests/frame/test_api.py b/pandas/tests/frame/test_api.py
--- a/pandas/tests/frame/test_api.py
+++ b/pandas/tests/frame/test_api.py
@@ -68,6 +68,14 @@ def test_add_prefix_suffix(self):
         expected = pd.Index(['%s#foo' % c for c in self.frame.columns])
         tm.assert_index_equal(with_suffix.columns, expected)
 
+        with_pct_prefix = self.frame.add_prefix('%')
+        expected = pd.Index(['%{}'.format(c) for c in self.frame.columns])
+        tm.assert_index_equal(with_pct_prefix.columns, expected)
+
+        with_pct_suffix = self.frame.add_suffix('%')
+        expected = pd.Index(['{}%'.format(c) for c in self.frame.columns])
+        tm.assert_index_equal(with_pct_suffix.columns, expected)
+
 
 class TestDataFrameMisc(SharedWithSparse, TestData):