Given the following data frame:
import pandas as pd
df = pd.DataFrame(
{'A':['A','B','C','D'],
'C':[12355.00,12555.67,640.00,7000]
})
df
A C
0 A 12355.00
1 B 12555.67
2 C 640.00
3 D 7000.00
I'd like to convert the values to dollars in thousands of USD like this:
A C
0 A $12.3K
1 B $12.5K
2 C $0.6K
3 D $7.0K
The second thing I need to do is somehow get these into a Seaborn heat map, which only accepts floats and integers. See here for more on the heat map aspect.
I'm assuming once the floats are converted to currency, they will be in object format but I'm hoping there's a way around that.
Or you could use a lambda function for shorter syntax
df['C'] = df['C'].apply(lambda x: "${:.1f}k".format((x/1000)))
def format(x):
return "${:.1f}K".format(x/1000)
df = pd.DataFrame(
{'A':['A','B','C','D'],
'C':[12355.00,12555.67,640.00,7000]
})
df['C'] = df['C'].apply(format)
print(df)
Or you could use a lambda function and f-string for even shorter shorter syntax
df['C'] = df['C'].map(lambda x: f"${x/1000:.1f}k")
Flexibile formatting using Babel
If you are looking for a flexible way of formatting currency's and numbers for different locale's, I suggest using Babel:
Example data
df = pd.DataFrame(
{'A':['A','B','C','D'],
'C':[12355.00,12555.67,640.00,7000]
})
print(df)
A C
0 A 12355.00
1 B 12555.67
2 C 640.00
3 D 7000.00
Formatting dollar currency:
from babel.numbers import format_currency
df["C"] = df["C"].apply(lambda x: format_currency(x, currency="USD", locale="en_US"))
A C
0 A $12,355.00
1 B $12,555.67
2 C $640.00
3 D $7,000.00
Formatting euro currency (notice the difference in thousands and decimal seperator):
df["C"] = df["C"].apply(lambda x: format_currency(x, currency="EUR", locale="nl_NL"))
A C
0 A € 12.355,00
1 B € 12.555,67
2 C € 640,00
3 D € 7.000,00
Related
I have a pandas DataFrame whose values I want to conditionally change into strings without looping over every value.
Example input:
In [1]: df = pd.DataFrame(data = [[1,2], [4,5]], columns = ['a', 'b'])
Out[2]:
a b
0 1 2
1 4 5
This is my best attempt which doesn't work properly
df['a'] = np.where(df['a'] < 3, f'string-{df["a"]}', df['a'])
In [1]: df
Out[2]:
a b
0 string0 1\n1 4\nName: a, dtype: int64 2
1 4 5
Desired output:
Out[2]:
A B
0 string-1 2
1 4 5
I am using np.where() since looping is not feasible due to the size of the actual DataFrame. The actual f-string I am using is also more complex and has two variables that include column names, but the problem is the same.
Are there other ways to conditionally change pandas values into f-strings without looping over each value?
You can use .map() together with f-string, as follows:
df['a'] = df['a'].map(lambda x: f'string-{x}' if x < 3 else x)
Alternatively, you can also use .loc together with string concatenation, as follows:
df.loc[df['a'] < 3, 'a'] = 'string-' + df['a'].astype(str)
#OR
df['a']=np.where(df['a'] < 3, 'string-'+df['a'].astype(str), df['a'])
Result:
print(df)
a b
0 string-1 2
1 4 5
My goal is to conditionally index a data frame and change the values in a column for these indexes.
I intend on looking through the column 'A' to find entries = 'a' and update their column 'B' with the word 'okay.
group = ['a']
df = pd.DataFrame({"A": [a,b,a,a,c], "B": [NaN,NaN,NaN,NaN,NaN]})
>>>df
A B
0 a NaN
1 b NaN
2 a NaN
3 a NaN
4 c NaN
df[df['A'].apply(lambda x: x in group)]['B'].fillna('okay', inplace=True)
This gives me the following error:
SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
self._update_inplace(new_data)
Following the documentation (what I understood of it) I tried the following instead:
df[df['A'].apply(lambda x: x in group)].loc[:,'B'].fillna('okay', inplace=True)
I can't figure out why the reassignment of 'NaN' to 'okay' is not occurring inplace and how this can be rectified?
Thank you.
Try this with lambda:
Solution First:
>>> df
A B
0 a NaN
1 b NaN
2 a NaN
3 a NaN
4 c NaN
Using lambda + map or apply..
>>> df["B"] = df["A"].map(lambda x: "okay" if "a" in x else "NaN")
OR# df["B"] = df["A"].map(lambda x: "okay" if "a" in x else np.nan)
OR# df['B'] = df['A'].apply(lambda x: 'okay' if x == 'a' else np.nan)
>>> df
A B
0 a okay
1 b NaN
2 a okay
3 a okay
4 c NaN
Solution second:
>>> df
A B
0 a NaN
1 b NaN
2 a NaN
3 a NaN
4 c NaN
another fancy way to Create Dictionary frame and apply it using map function across the column:
>>> frame = {'a': "okay"}
>>> df['B'] = df['A'].map(frame)
>>> df
A B
0 a okay
1 b NaN
2 a okay
3 a okay
4 c NaN
Solution Third:
This is already been posted by #d_kennetz but Just want to club together, wher you can also do the assignment to both columns (A & B)in one shot:..
>>> df.loc[df.A == 'a', 'B'] = "okay"
If I understand this correctly, you simply want to replace the value for a column on those rows matching a given condition (i.e. where A column belongs to a certain group, here with a single value 'a'). The following should do the trick:
import pandas as pd
group = ['a']
df = pd.DataFrame({"A": ['a','b','a','a','c'], "B": [None,None,None,None,None]})
print(df)
df.loc[df['A'].isin(group),'B'] = 'okay'
print(df)
What we're doing here is we're using the .loc filter, which just returns a view on the existing dataframe.
First argument (df['A'].isin(group)) filters on those rows matching a given criterion. Notice you can use the equality operator (==) but not the in operator and therefore have to use .isin() instead).
Second argument selects only the 'B' column.
Then you just assign the desired value (which is a constant).
Here's the output:
A B
0 a None
1 b None
2 a None
3 a None
4 c None
A B
0 a okay
1 b None
2 a okay
3 a okay
4 c None
If you wanted to fancier stuff, you might want do the following:
import pandas as pd
group = ['a', 'b']
df = pd.DataFrame({"A": ['a','b','a','a','c'], "B": [None,None,None,None,None]})
df.loc[df['A'].isin(group),'B'] = "okay, it was " + df['A']+df['A']
print(df)
Which gives you:
A B
0 a okay, it was aa
1 b okay, it was bb
2 a okay, it was aa
3 a okay, it was aa
4 c None
Let's say I have a data frame with such column names:
['a','b','c','d','e','f','g']
And I would like to change names from 'c' to 'f' (actually add string to the name of column), so the whole data frame column names would look like this:
['a','b','var_c_equal','var_d_equal','var_e_equal','var_f_equal','g']
Well, firstly I made a function that changes column names with the string i want:
df.rename(columns=lambda x: 'or_'+x+'_no', inplace=True)
But now I really want to understand how to implement something like this:
df.loc[:,'c':'f'].rename(columns=lambda x: 'var_'+x+'_equal', inplace=True)
You can a use a list comprehension for that like:
Code:
new_columns = ['var_{}_equal'.format(c) if c in 'cdef' else c for c in columns]
Test Code:
import pandas as pd
df = pd.DataFrame({'a':(1,2), 'b':(1,2), 'c':(1,2), 'd':(1,2)})
print(df)
df.columns = ['var_{}_equal'.format(c) if c in 'cdef' else c
for c in df.columns]
print(df)
Results:
a b c d
0 1 1 1 1
1 2 2 2 2
a b var_c_equal var_d_equal
0 1 1 1 1
1 2 2 2 2
One way is to use a dictionary instead of an anonymous function. Both the below variations assume the columns you need to rename are contiguous.
Contiguous columns by position
d = {k: 'var_'+k+'_equal' for k in df.columns[2:6]}
df = df.rename(columns=d)
Contiguous columns by name
If you need to calculate the numerical indices:
cols = df.columns.get_loc
d = {k: 'var_'+k+'_equal' for k in df.columns[cols('c'):cols('f')+1]}
df = df.rename(columns=d)
Specifically identified columns
If you want to provide the columns explicitly:
d = {k: 'var_'+k+'_equal' for k in 'cdef'}
df = df.rename(columns=d)
I need to run a task which can be done with a loop, but I imagine that there is a more efficient and pretty way to do this. I have a DataFrame which has an integer column, which I would like to transform to a 4-digit string representation. That is, 3 should be converted to '0003', 234 should be converted to '0234'. I am looking for a vector operation that will do this to all entries in the column at once, quickly with simple code.
you can use Series.str.zfill() method:
df['column_name'] = df['column_name'].astype(str).str.zfill(4)
Demo:
In [29]: df = pd.DataFrame({'a':[1,2], 'b':[3,234]})
In [30]: df
Out[30]:
a b
0 1 3
1 2 234
In [31]: df['b'] = df['b'].astype(str).str.zfill(4)
In [32]: df
Out[32]:
a b
0 1 0003
1 2 0234
You can also do this using the Series.apply() method and an f-string wrapped in a lambda function:
In [1]: import pandas as pd
In [2]: df = pd.DataFrame({'a':[1,2], 'b':[3,234]})
In [3]: df
Out[3]:
a b
0 1 3
1 2 234
In [4]: df['b'] = df['b'].apply(lambda x: f"{x:04d}")
In [5]: df
Out[5]:
a b
0 1 0003
1 2 0234
In the f-string, the part after the colon says "zero-pad the field with four characters and make it a signed base-10 integer".
I have a Pandas DataFrame and all the value are strs, I want to get the first 4 characters of every value. And I wonder are there any built-in functions that can do this.
I can do this by using for loop:
>>> import pandas as pd
>>> df = pd.DataFrame(my_data, columns=my_columns)
>>> for values in df.iteritems():
for value in values[1].tolist():
print value[0:4]
But, that's hard to read and not pythonic.
You can call apply with a lambda that calls the vectorise str methods to slice your strings:
In [136]:
df = pd.DataFrame({'a':['asdas','asdasdsadas','123124'],'b':['554653645','546456453634','uyiyasdhnfjnas']})
df
Out[136]:
a b
0 asdas 554653645
1 asdasdsadas 546456453634
2 123124 uyiyasdhnfjnas
In [138]:
df.apply(lambda x: x.str[:4])
Out[138]:
a b
0 asda 5546
1 asda 5464
2 1231 uyiy