43

I'm looking for the opposite to this Q&A: Convert an excel or spreadsheet column letter to its number in Pythonic fashion.

or this one but in python How to convert a column number (eg. 127) into an excel column (eg. AA)

Community
  • 1
  • 1
jason
  • 2,953
  • 16
  • 80
  • 130
  • did u try that in python – sundar nataraj May 26 '14 at 02:32
  • @sundar nataraj サンダーナタラジ i sure did. I tried the code here: http://bytes.com/topic/python/answers/45129-convert-numbers-letters . I tried a few examples in there. But since the site is so horribly organized. I can't figure what code works and doesn't. Even people getting answers on that page can't figure it out. – jason May 26 '14 at 02:47

14 Answers14

107
start_index = 1   #  it can start either at 0 or at 1
letter = ''
while column_int > 25 + start_index:   
    letter += chr(65 + int((column_int-start_index)/26) - 1)
    column_int = column_int - (int((column_int-start_index)/26))*26
letter += chr(65 - start_index + (int(column_int)))
sundar nataraj
  • 8,200
  • 2
  • 30
  • 44
  • 1
    Any idea how that example can be modified for a zero rather than 1-based solution? A -> 0, B -> 1 .... my modification so far: ```n += 1```before the while loop – RandomDude Nov 02 '17 at 16:00
  • Starting with n += 1 is probably the simplest way to do it. There's no symbol for "0", so it's not a normal base 26 system. In base ten, "20" represents 2*10+0*1. In the spreadsheet, you can't represent 2*26+0*1. You have to use 1*26+26*1, which is "AZ". See https://paradise.caltech.edu/ist4/lectures/foster%201947%20decimal%20no%20zero%20number%20system.pdf – Jonathan Richards Nov 22 '19 at 00:07
  • 2
    Strings are immutable, thus, prepending to a string is expensive since everything must move. Consider using a mutable alternative like a `list` and converting to string `"".join(lst)` – Brayoni May 06 '20 at 07:17
43

The xlsxwriter library includes a conversion function, xlsxwriter.utility.xl_col_to_name(index) and is on github

here is a working example:

>>> import xlsxwriter 
>>> xlsxwriter.utility.xl_col_to_name(10)
'K'
>>> xlsxwriter.utility.xl_col_to_name(1)
'B'
>>> xlsxwriter.utility.xl_col_to_name(0)
'A'

Notice that it's using zero-indexing.

Mohamed Ali JAMAOUI
  • 13,233
  • 12
  • 67
  • 109
travisa
  • 877
  • 8
  • 12
  • 4
    This library is great, it's a good idea to use a well know working library instead of custom code – rebrec Aug 07 '15 at 14:23
  • +1 , but xlsxwriter.utility.xl_col_to_name(28) is giving `AC` instead of `AB`, By subtracting input number with 1 it is giving expected output as `AB`, but do you know why it is happening – akash karothiya Sep 27 '16 at 13:11
  • 4
    @akashkarothiya xlsxwriter uses 0-based indexing (as does python) – Dnaiel May 03 '17 at 12:19
  • 1
    Also the [xlrd](https://xlrd.readthedocs.io/en/latest/index.html) library has a [conversion function](https://xlrd.readthedocs.io/en/latest/api.html#xlrd.formula.colname): `xlrd.formula.colname(0)` returns `'A'` – Giancarlo Sportelli Jun 23 '19 at 16:50
20

The openpyxl library includes the conversion function (amongst others) which you are looking for, get_column_letter:

>>> from openpyxl.utils.cell import get_column_letter
>>> get_column_letter(1)
'A'
>>> get_column_letter(10)
'J'
>>> get_column_letter(3423)
'EAQ'
Roman
  • 564
  • 5
  • 11
13

My recipe for this was inspired by another answer on arbitrary base conversion (https://stackoverflow.com/a/24763277/3163607)

import string

def n2a(n,b=string.ascii_uppercase):
   d, m = divmod(n,len(b))
   return n2a(d-1,b)+b[m] if d else b[m]

Example:

for i in range(23,30):
    print (i,n2a(i))

outputs

23 X
24 Y
25 Z
26 AA
27 AB
28 AC
29 AD
Community
  • 1
  • 1
Giancarlo Sportelli
  • 1,050
  • 1
  • 15
  • 20
7

Edited after some tough love from Meta

The procedure for this involves dividing the number by 26 until you've reached a number less than 26, taking the remainder each time and adding 65, since 65 is where 'A' is in the ASCII table. Read up on ASCII if that doesn't make sense to you.

Note that like the originally linked question, this is 1-based rather than zero-based, so A -> 1, B -> 2.

def num_to_col_letters(num):
    letters = ''
    while num:
        mod = (num - 1) % 26
        letters += chr(mod + 65)
        num = (num - 1) // 26
    return ''.join(reversed(letters))

Example output:

for i in range(1, 53):
    print(i, num_to_col_letters(i))
1 A
2 B
3 C
4 D
...
25 Y
26 Z
27 AA
28 AB
29 AC
...
47 AU
48 AV
49 AW
50 AX
51 AY
52 AZ
Guillaume Jacquenot
  • 10,118
  • 5
  • 41
  • 48
Marius
  • 54,802
  • 15
  • 100
  • 97
  • 5
    This gives wrong output for `num_to_col_letters(26)` it outputs `"A@"` – jspurim May 26 '14 at 03:30
  • 1
    I saw the edit proposed by @coldfix - this answer doesn't work and a working version was proposed in good faith as an edit. For full disclosure: I wasn't sure how to deal with that and [asked this question on meta about it](http://meta.stackoverflow.com/questions/288430). Anyway - my recommendation is that the edit would be better added as a new answer - or that the original answer should have a look at [that edit](http://stackoverflow.com/review/suggested-edits/7414196) and decide whether to change this answer. – J Richard Snape Mar 20 '15 at 16:31
  • 1
    P.S. The [answer by @user1344186](http://stackoverflow.com/a/28782635/838992) works on these edge cases properly using a similar approach. – J Richard Snape Mar 20 '15 at 16:34
  • 2
    @JRichardSnape I agree that my answer is basically broken and the edit more or less provides a brand new algorithm rather than fixing minor problems with mine. – Marius Mar 21 '15 at 00:12
  • 2
    @jason_cant_code still seems to be active on the site so maybe they can be persuaded to switch the accepted answer. – Marius Mar 21 '15 at 00:13
  • @Marius Well, why don't you just fix the answer? The point of this site is to provide correct information, and you yourself fixing wrong answer of your own certainly is not defacing or anything. – hyde Mar 22 '15 at 07:19
  • 1
    @hyde The answer should be fixed now, although it might be too late to reverse the effects of the hammering it took from Meta. – Marius Mar 22 '15 at 23:55
  • Sorry - i didn't expect the hammering that my approach triggered. I'll certainly be more cautious about linking to anything from meta again - I was just trying to query my own actions not yours. I think you've done all the right things and have reversed my downvote FWIW. – J Richard Snape Mar 24 '15 at 16:56
7

Just for people still interest in this. The chosen answer by @Marius gives wrong outputs in some cases, as commented by @jspurim. Here is the my answer.

import string
def convertToTitle(num):
    title = ''
    alist = string.uppercase
    while num:
        mod = (num-1) % 26
        num = int((num - mod) / 26)  
        title += alist[mod]
    return title[::-1]
Luke
  • 622
  • 1
  • 9
  • 19
4

Recursive one line solution w/o libraries

def column(num, res = ''):
   return column((num - 1) // 26, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[(num - 1) % 26] + res) if num > 0 else res
Axalix
  • 2,716
  • 1
  • 19
  • 35
2
def _column(aInt):
    return chr(((aInt - 1) / 26)+ 64) + chr(((aInt - 1) % 26) + 1 + 64) if aInt > 26 else chr(aInt + 64)
    
print _column(1)
print _column(50)
print _column(100)
print _column(260)
print _column(270)

Output: A AX CV IZ JJ

1

This simple Python function works for columns with 1 or 2 letters.

def let(num):       

alphabeth = string.uppercase
na = len(alphabeth)

if num <= len(alphabeth):
    letters = alphabeth[num-1]
else:
    letters = alphabeth[ ((num-1) / na) - 1 ] +  alphabeth[((num-1) % na)]            

return letters
Alex Benfica
  • 398
  • 4
  • 15
1

Recursive Implementation

import string
def spreadsheet_column_encoding_reverse_recursive(x):
    def converter(x):
        return (
            ""
            if x == 0
            else converter((x - 1) // 26) + string.ascii_uppercase[(x - 1) % 26]
        )

    return converter(x)

Iterative Implementations

Version 1: uses chr, ord

def spreadsheet_column_encoding_reverse_iterative(x):
    s = list()

    while x:
        x -= 1
        s.append(chr(ord("A") + x % 26))
        x //= 26

    return "".join(reversed(s))

Version 2: Uses string.ascii_uppercase

import string
def spreadsheet_column_encoding_reverse_iterative(x):
    s = list()

    while x:
        x -= 1
        s.append(string.ascii_uppercase[x % 26])
        x //= 26

    return "".join(reversed(s))

Version 3: Uses divmod, chr, ord

def spreadsheet_column_encoding_reverse_iterative(x):
    s = list()

    while x:
        x, remainder = divmod(x - 1, 26)
        s.append(chr(ord("A") + remainder))

    return "".join(reversed(s))
Brayoni
  • 586
  • 4
  • 14
1

Here is a recursive solution:

def column_num_to_string(n):
    n, rem = divmod(n - 1, 26)
    char = chr(65 + rem)
    if n:
        return column_num_to_string(n) + char
    else:
        return char

column_num_to_string(28)
#output: 'AB'

The inverse can also be defined recursively, in a similar way:

def column_string_to_num(s):
    n = ord(s[-1]) - 64
    if s[:-1]:
        return 26 * (column_string_to_num(s[:-1])) + n
    else:
        return n
    
column_string_to_num("AB")
#output: 28
0

Just to complicate everything a little bit I added caching, so the name of the same column will be calculated only once. The solution is based on a recipe by @Alex Benfica

import string


class ColumnName(dict):
    def __init__(self):
        super(ColumnName, self).__init__()
        self.alphabet = string.uppercase
        self.alphabet_size = len(self.alphabet)

    def __missing__(self, column_number):
        ret = self[column_number] = self.get_column_name(column_number)
        return ret

    def get_column_name(self, column_number):
        if column_number <= self.alphabet_size:
            return self.alphabet[column_number - 1]
        else:
            return self.alphabet[((column_number - 1) / self.alphabet_size) - 1] + self.alphabet[((column_number - 1) % self.alphabet_size)]

Usage example:

column = ColumnName()

for cn in range(1, 40):
    print column[cn]

for cn in range(1, 50):
    print column[cn]
Andrei
  • 107
  • 8
  • A couple issues with this implementation: it accepts negative numbers, where intuitively it should error; it fails on columns of three or more characters (eg `column[703]` should be `AAA`) – kevinsa5 Feb 16 '18 at 14:57
0
import math

num = 3500
row_number = str(math.ceil(num / 702))
letters = ''
num = num - 702 * math.floor(num / 702)
while num:
    mod = (num - 1) % 26
    letters += chr(mod + 65)
    num = (num - 1) // 26
result = row_number + ("".join(reversed(letters)))
print(result)
Ricky
  • 109
  • 1
  • 9
0
import gspread

def letter2num(col_letter: str) -> int:
    row_num, col_num = gspread.utils.a1_to_rowcol(col_letter + '1')
    return col_num


def num2letter(col_num: int) -> str:
    return gspread.utils.rowcol_to_a1(1, col_num)[:-1]


# letter2num('D') => returns 4
# num2letter(4) => returns 'D'
George C
  • 1,028
  • 13
  • 29