Dataset

Name and source

S-FFSD, the Simulated Financial Fraud Semi-supervised Dataset.

It ships inside the AI4Risk/antifraud research repository.

Download it with:

python -m spark.data.fetch

Why I chose it

Fields

Field Meaning
Time Position in the transaction sequence, 0 to 77,880
Source Customer account paying
Target Merchant receiving
Amount Transaction amount
Location Where the transaction happened
Type Payment channel or instrument class
Labels 0 normal, 1 fraud, 2 unknown

What the fraud label means

Value Meaning
0 Confirmed normal
1 Confirmed fraud
2 Never confirmed either way

Counts:

Label Rows Share
0 normal 24,387 31.3%
1 fraud 5,256 6.7%
2 unknown 48,238 61.9%

Among labeled rows, 17.7% are fraud.

The unknown rows are not thrown away. They are real traffic, so they still count towards velocity and still appear in the graph. They are only left out of model fitting and out of every reported number.

Size

Transactions 77,881
Customer accounts 30,346
Merchants 886
Locations 296
Payment channels 166

Splits

Split by time, never shuffled.

Split Rows Time range Labeled Fraud Fraud rate
Train 54,516 0 to 54,515 20,806 2,573 0.1237
Validation 11,682 54,516 to 66,197 3,737 445 0.1191
Test 11,683 66,198 to 77,880 5,100 2,238 0.4388

Why the test data is kept separate

If you pick your settings by looking at the test data, the test score stops meaning anything. It becomes a description of tuning, not a prediction of how the system will behave on new data.

So:

Why split by time and not randomly

The fraud rate in this file more than triples from start to end:

Part of the file Fraud rate
First tenth 0.1290
Last tenth 0.4244

A random split would spread the fraud ring across train and test. The model would then see part of the ring during training and score the rest easily. That looks great and means nothing.

Splitting by time keeps the ring where it actually happened. It makes the test harder and honest.

The ring in this data

Merchant T1822:

Transactions 1,433
Different accounts 1,411
Payment channels used 1
Locations used 1
Time span one burst
Confirmed fraud 98.6%

Almost every transaction comes from a different account. That is the shape of a ring using throwaway accounts, not a shop with repeat customers.

The same channel TP110 also carries merchant T1015 at 82.6% fraud.

What is missing

The dataset does not have:

Field Status
Device ID Not available
IP address Not available
Email or domain Not available
Card number Not available
Real timestamps Not available, Time is a sequence position

The system does not invent these. Run python -m spark.data.inspect and it prints a table marking each one.

Device and IP are two of the strongest ring signals in real fraud work. Without them, this project uses merchant, payment channel, and location instead.

Limitations

Other datasets I looked at

Dataset Why I did not use it
IEEE-CIS Fraud Detection Needs a Kaggle account, and 339 of its columns are anonymised, so explanations would read like "V257 raised the score", which nobody can act on
PaySim The balance columns give away the answer, because fraudulent transfers are cancelled in the simulation
YelpChi Review spam, not payments. No amount, no merchant loss
Amazon Fake reviewer accounts, not payments. The label is a proxy, not confirmed fraud
Sparkov, BankSim Fraud is generated one account at a time, so there are no coordinated groups to find