39

I need a way to change the specific value of a column of a CSV file. For example I have this CSV file:

"Ip","Sites"
"127.0.0.1",10
"127.0.0.2",23
"127.0.0.3",50

and I need to change the value 23 to 30 of the row "127.0.0.2".

I use csv library: import csv

Gino Mempin
  • 25,369
  • 29
  • 96
  • 135
ihorko
  • 6,855
  • 25
  • 77
  • 116
  • So, I can read from csv file and write to csv file, but I haven't an idea how to repace values in existing file... – ihorko Jun 14 '12 at 13:11
  • Could you post the read/write code that you already have? At some point you should have the data in Python and you can modify it there before writing. – Simeon Visser Jun 14 '12 at 13:13

4 Answers4

65

This is the solution opening the csv file, changing the values in memory and then writing back the changes to disk.

r = csv.reader(open('/tmp/test.csv')) # Here your csv file
lines = list(r)

Content of lines:

[['Ip', 'Sites'],
 ['127.0.0.1', '10'],
 ['127.0.0.2', '23'],
 ['127.0.0.3', '50']]

Modifying the values:

lines[2][1] = '30'

Content of lines:

[['Ip', 'Sites'],
 ['127.0.0.1', '10'],
 ['127.0.0.2', '30'],
 ['127.0.0.3', '50']]

Now we only have to write it back to a file

writer = csv.writer(open('/tmp/output.csv', 'w'))
writer.writerows(lines)
Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
Diego Navarro
  • 9,316
  • 3
  • 26
  • 33
  • 2
    If you run into a double linebreak problem on the written csv, you might want to try openning the file as a binary: `writer = csv.writer(open('/tmp/output.csv', 'wb'))` – Nitay Mar 04 '15 at 10:28
  • 3
    Do you have to close the writer? – pekasus Jul 13 '17 at 13:57
  • 2
    You should close the file descriptor but not the writer. You can use "with" to manage files – Diego Navarro Jul 17 '17 at 09:56
  • In my case; not closing the writer was causing issues. Updated the answer to use "with" – lobi May 08 '20 at 16:49
  • Note: if you're doing this a lot, it's **highly uneffective** because every change of value makes a full rewrite of the whole file. (But ok, there is probably no other way for a CSV) – Basj Dec 07 '20 at 08:39
25

You can use very powerful library called pandas. Here is the example.

import pandas as pd
df = pd.read_csv("test.csv")
df.head(3) #prints 3 heading rows

Output:

    Ip  Sites
0   127.0.0.1   10
1   127.0.0.2   23
2   127.0.0.3   50

Now if you want to change the value in the 'Sites' column in the 1st row, run:

df.set_value(1, "Sites", 30)

If you want to change all the values, where 'Ip' is equal to 127.0.0.2, run:

df.loc[df["Ip"]=="127.0.0.2", "Sites"] = 30

Finally, to save the values:

df.to_csv("test.csv", index=False)
Yury
  • 20,618
  • 7
  • 58
  • 86
  • 1
    Though it works for most of the time. Sometimes it gives `PermissionError: [Errno 13] Permission denied: 'file.csv'` Can you suspect what could be the reason? – prashantitis Mar 30 '18 at 12:34
  • 1
    This is not related to this code. It seems that you do not have write permissions to the file. – Yury Apr 03 '18 at 06:42
  • 4
    this is because the file is still open. close it out and you're all good. – RobK Mar 10 '20 at 11:53
18

You can't really replace values in the existing file. Instead, you need to:

  1. read in existing file
  2. alter file in memory
  3. write out new file (overwriting existing file)

What you can also do is read in the existing file line by line, writing it out to a new file, while replacing values on the fly. When done, close both files, delete the original and rename the new file.

Daren Thomas
  • 67,947
  • 40
  • 154
  • 200
7

An alternative to the accepted answer is to:

  • Use fileinput with inplace=True to modify the file in-place
  • Use csv.DictReader to access the column via header instead of indices (This only works if the CSV has headers)

Test CSV:

Ip,Sites
127.0.0.1,10
127.0.0.2,23
127.0.0.3,50

Test Code:

import fileinput

with fileinput.input(files=('test.csv'), inplace=True, mode='r') as f:
    reader = csv.DictReader(f)
    print(",".join(reader.fieldnames))  # print back the headers
    for row in reader:
        if row["Ip"] == "127.0.0.2":
            row["Sites"] = "30"
        print(",".join([row["Ip"], row["Sites"]]))

The main difference is that you don't have to manually open the input file and create the output file, as inplace=True already does that behind-the-scenes:

Optional in-place filtering: if the keyword argument inplace=True is passed to fileinput.input() or to the FileInput constructor, the file is moved to a backup file and standard output is directed to the input file (if a file of the same name as the backup file already exists, it will be replaced silently). This makes it possible to write a filter that rewrites its input file in place.

The loop goes over the CSV row-by-row (except for the header row), so you can do whatever processing you need on each row.

If you still want to retain the original, you could pass in a backup=".backup" so that fileinput creates a test.csv.backup file.

Also, note that with in-place editing, "standard output is directed to the input file", so print(..) prints it out to the file instead of to the command line. If you want to actually print to the console, you need to specify stderr as in print(..., file=sys.stderr).

Gino Mempin
  • 25,369
  • 29
  • 96
  • 135