Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# ISIC Classification Utilities

This repository contains a simple ISIC Rev.4 classifier and a small demo app
built with Streamlit. The classifier maps free‑text firm activity descriptions
to four‑digit ISIC codes using token overlap and fuzzy string matching.

## Installation

Create a virtual environment and install the dependencies:

```bash
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```

Alternatively, you can install the minimal packages directly:

```bash
pip install pandas streamlit
```

## Running the Streamlit app

Launch the web interface with:

```bash
streamlit run isic_app.py
```

The app lets you classify a single activity description or upload a CSV file
(with a `d1a1x` column) and obtain the predicted ISIC codes. The processed file
can be downloaded directly from the app.

## Command line usage

The `isic_classifier.py` module can also be run from the command line:

```bash
python isic_classifier.py input.csv output.csv
```

The input CSV must contain a `d1a1x` column. The script will produce a copy of
the file with `isic_code` and `match_score` columns added.

## Testing

Run the tests with:

```bash
pytest -q
```
20 changes: 20 additions & 0 deletions isic.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
0111;Growing of cereals (except rice), leguminous crops and oil seeds
0112;Growing of rice
0113;Growing of vegetables and melons, roots and tubers
0114;Growing of sugar cane
0116;Growing of fibre crops
0311;Marine fishing
0510;Mining of hard coal
1010;Processing and preserving of meat
1071;Manufacture of bakery products
1811;Printing
2011;Manufacture of basic chemicals
4610;Wholesale on a fee or contract basis
4711;Retail sale in non-specialized stores with food, beverages or tobacco predominating
5510;Short term accommodation activities
5610;Restaurants and mobile food service activities
6201;Computer programming activities
7020;Management consultancy activities
8610;Hospital activities
9311;Operation of sports facilities
9602;Hairdressing and other beauty treatment
39 changes: 39 additions & 0 deletions isic_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import io
import pandas as pd
import streamlit as st

from isic_classifier import load_isic, classify

st.title("ISIC Rev.4 Classifier")

isic_data = load_isic()

st.header("Classify a single activity")
text = st.text_input("Activity description")
if st.button("Classify"):
if text:
code, score = classify(text, isic_data)
st.write(f"**ISIC code:** {code}")
st.write(f"**Match score:** {score:.2f}")
else:
st.warning("Please enter a description")

st.header("Classify activities from a CSV file")
file = st.file_uploader("Upload CSV with a 'd1a1x' column", type=["csv"])
if file is not None:
df = pd.read_csv(file)
if "d1a1x" not in df.columns:
st.error("Column 'd1a1x' not found in uploaded file")
else:
codes = []
scores = []
for desc in df["d1a1x"].astype(str):
code, score = classify(desc, isic_data)
codes.append(code)
scores.append(round(score, 2))
df["isic_code"] = codes
df["match_score"] = scores
st.dataframe(df)
csv = df.to_csv(index=False).encode("utf-8")
st.download_button("Download results", csv, "isic_results.csv", "text/csv")

65 changes: 65 additions & 0 deletions isic_classifier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import csv
import re
import sys
from difflib import SequenceMatcher
from typing import List, Tuple


def load_isic(path: str = "isic.csv") -> List[Tuple[str, str]]:
"""Load ISIC code descriptions from a semicolon separated file."""
with open(path, newline='', encoding='utf-8') as f:
reader = csv.reader(f, delimiter=';')
return [(code.strip(), desc.strip()) for code, desc in reader]


def _tokenize(text: str) -> set:
"""Return a set of word tokens from the given text."""
return set(re.findall(r"\b\w+\b", text.lower()))


def _ratio(a: str, b: str) -> float:
"""Return a fuzzy string matching ratio between 0 and 1."""
return SequenceMatcher(None, a.lower(), b.lower()).ratio()


def classify(activity: str, isic_data: List[Tuple[str, str]]) -> Tuple[str, float]:
"""Return the ISIC code with the best combined similarity score."""
tokens = _tokenize(activity)
best_code = None
best_score = 0.0
for code, desc in isic_data:
desc_tokens = _tokenize(desc)
if not desc_tokens:
continue
token_overlap = len(tokens & desc_tokens) / len(desc_tokens)
ratio = _ratio(activity, desc)
score = 0.5 * token_overlap + 0.5 * ratio
if score > best_score:
best_score = score
best_code = code
return best_code, best_score


def classify_file(input_csv: str, output_csv: str, column: str = "d1a1x", isic_path: str = "isic.csv") -> None:
"""Classify activities in a CSV file and write results to a new CSV."""
isic_data = load_isic(isic_path)
with open(input_csv, newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
rows = list(reader)

for row in rows:
code, ratio = classify(row[column], isic_data)
row['isic_code'] = code
row['match_score'] = f"{ratio:.2f}"

with open(output_csv, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
writer.writeheader()
writer.writerows(rows)


if __name__ == "__main__":
if len(sys.argv) == 3:
classify_file(sys.argv[1], sys.argv[2])
else:
print("Usage: python isic_classifier.py input.csv output.csv")
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pandas
streamlit
6 changes: 6 additions & 0 deletions sample_activities.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
d1a1x
Growing of rice
Computer software development
Restaurants
Short term accommodation services
Operation of sports stadium
26 changes: 26 additions & 0 deletions test_classifier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import csv
import os
from isic_classifier import classify_file


def test_classify_file(tmp_path):
input_csv = os.path.join(tmp_path, 'input.csv')
output_csv = os.path.join(tmp_path, 'output.csv')
# copy sample activities file
with open('sample_activities.csv', 'r', encoding='utf-8') as src:
data = src.read()
with open(input_csv, 'w', encoding='utf-8') as dst:
dst.write(data)

classify_file(input_csv, output_csv)

with open(output_csv, newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
results = list(reader)

codes = [row['isic_code'] for row in results]
assert codes[0] == '0112'
assert codes[1] == '6201'
assert codes[2] == '5610'
assert codes[3] == '5510'
assert codes[4] == '9311'