DS-0005

Factory-reset classifier set

A teaching set for supervised learning. Each row is an invented Android phone described by 22 features of the kind an examiner reads, and the question is yes or no: was it factory reset in the last 30 days? There are train and test files, a hand-written baseline, an evaluation script and a teaching guide. Every row is synthetic.

Synthetic tableVersion 1.0Data CC BY 4.0Code MIT

At a glance

What the set is, how it was made, and what it is for.

Plain CSV, so a spreadsheet, a SQLite browser or a text editor opens it. Try the challenge: Train and doubt a reset classifier (Practitioner).

Task
Binary classification: reset_30d is 1 if the phone's most recent factory reset was 30 days or less before acquisition
Device class
None (generated table of Android-style artifact features)
OS
Not applicable
Evidence type
Synthetic table
Made on
A script (generate.py, Python standard library) with seed 20261106. No device, no case data and no real identifiers. The same seed gives the same bytes.
Contents
2,000 rows in the main set: 1,400 to train on and 600 to test on, with 600 positives (30 percent). A variant with rare positives has 2,000 more rows and 100 positives (5 percent).
Built-in traps
A leakage column, a class-imbalance variant, clock-tampered rows, look-alike phones and blanks that mean different things
Size
179 KB as a zip (182,901 bytes), 17 files
Version
1.0
Licence
Data: CC BY 4.0. Code: MIT.
Maintainer
Daniel Addai, SectraTEK Lab

What is inside

One zip. The test labels are in a separate file so you can predict blind and score afterwards.

factory-reset-classifier-1.0.zip
factory-reset-classifier/
  README.txt
  LICENCE.txt
  DATA_DICTIONARY.md
  data_dictionary.csv
  TEACHING_GUIDE.md
  train.csv
  test.csv
  test_answers.csv
  train_reference.csv
  variants/imbalanced/
    train.csv
    test.csv
    test_answers.csv
    train_reference.csv
  code/baseline_stdlib.py
  code/baseline_sklearn.py
  code/evaluate.py
  generator/generate.py
  • train.csv and test.csv: 1,400 and 600 rows, device_id plus the 22 features; train.csv also has the label reset_30d.
  • test_answers.csv: the test labels, kept apart, with the profile, true reset age and clock columns for slicing. train_reference.csv has the same extra columns for the train rows.
  • variants/imbalanced/: the same four files with about 5 percent positives.
  • DATA_DICTIONARY.md, data_dictionary.csv, TEACHING_GUIDE.md, README.txt, LICENCE.txt: documentation and licences.

The baseline in code/baseline_stdlib.py is a logistic regression and a decision stump written by hand with the Python standard library, so nothing needs installing. code/baseline_sklearn.py is optional and needs scikit-learn; it was checked for syntax only. code/evaluate.py prints accuracy, precision, recall, F1, balanced accuracy and the confusion matrix, for all rows or sliced by a column of the answer file.

The 22 features

Ages are days before the acquisition time, as the phone's own clock reports them. A blank cell means the artifact was not there or could not be read. The full dictionary is in DATA_DICTIONARY.md.

Features in DS-0005
ColumnKindBlank whenWhat it is
recovery_file_presentflag 0/1neverA recovery history file was found on the extraction. Absent on about one phone in four, as on a whole device generation in LAB-TR-0004.
recovery_entries_totalcount, entriesblank when recovery_file_present is 0Entries in the recovery history, of any kind (wipes, updates, region changes).
recovery_wipe_entriescount, entriesblank when recovery_file_present is 0Entries in the recovery history that are wipes (a --wipe_data command). Counts reach back before the 30-day window, so a high count is not a recent reset.
last_recovery_wipe_age_daysnumber, days before acquisitionblank when there is no file or no wipe entryAge of the most recent wipe entry, from the recovery clock as printed. On about one wipe-bearing phone in eight the recovery clock prints a year near 1970, so the age reads about 20,500 days. See recovery_clock_implausible.
last_recovery_entry_age_daysnumber, days before acquisitionblank when there is no file or no entryAge of the most recent entry of any kind, from the recovery clock as printed. Same implausible-clock problem as the wipe age.
recovery_clock_implausibleflag 0/1blank when recovery_file_present is 01 when the recovery clock printed a year before 2010 on the wipe-bearing history.
setup_complete_age_daysnumber, days before acquisitionabout 4 percent blankAge of the setup-wizard-completed flag, from its file time. A reset sets it again; so can a system update. Overlap: updated phones and new phones also have a recent value.
setup_flag_changes_90dcount, changesneverChanges to the setup flags in the 90 days before acquisition. Setup flags cycle around updates as well as resets (LAB-TR-0004).
boot_count_since_setupcount, bootsneverBoots counted from the setup flag time to acquisition. Heavy right tail: try a log scale.
boot_reason_history_rowscount, rowsneverRows in the boot-reason history. Written to carry no signal about resets; the real history never held a factory-reset entry in the Lab's study. A noise column on purpose: does your model give it weight?
user_app_countcount, appsneverUser-installed apps present at acquisition.
earliest_app_install_age_daysnumber, days before acquisitionneverAge of the oldest first-install time among user apps. A restore from backup or an app kept across the reset can push this back. A new phone also has a small value.
app_install_spread_daysnumber, daysneverNewest first-install time minus oldest, among user apps. Small on a restored phone, on a new phone and on a phone reset a few days ago.
apps_installed_30d_fractionnumber, fraction 0 to 1neverShare of user apps whose first-install time is within 30 days of acquisition.
account_added_age_daysnumber, days before acquisitionabout 3 percent blankAge of the primary account being added, from the account database. People re-add accounts, so an old phone can show a recent value.
account_countcount, accountsneverAccounts on the phone.
battery_stats_age_daysnumber, days before acquisitionneverAge of the start of the battery statistics. Weak: these statistics restart on charging and reboots, not only on resets.
storage_used_pctnumber, percentneverShare of user storage in use. Weak and overlapping between the classes.
epoch_zero_file_timeflag 0/1neverAt least one file time within a day of the Unix epoch (a clock check). Fires on about nine phones in ten whatever the label (LAB-TR-0022 reports 41 of 46). Not informative.
future_file_time_48hflag 0/1neverAt least one file time more than 48 hours after acquisition (a clock check). Fires on about one phone in three whatever the label. Not informative.
clock_set_since_bootflag 0/1neverThe phone's own record says its clock was set since boot. Only a partial pointer to the clock-tampered rows: it misses some and fires on some untouched phones.
reviewer_reset_noteflag 0/1neverA note a human reviewer typed after reading the recovery log: reset seen. LEAKAGE TRAP. Written after the question was answered, so it would not exist at triage time. Drop it before you train.

The feature ideas come from the reset-indicator study (LAB-TR-0004: recovery history, setup flags, boot-reason history, clocks that print an impossible year) and the clock-anomaly study (LAB-TR-0022: simple checks that fire on most ordinary phones). How the features relate to the label is invented.

Reference answers and traps

The label is exact by construction: each phone has a known reset age, and reset_30d is 1 when it is 30 days or less. test_answers.csv holds the label for every test row, and train_reference.csv and test_answers.csv add the columns an examiner would not have: the phone's profile, the true reset age (blank if never reset), whether its clock was tampered with, and the clock offset.

Phone profiles in DS-0005 and the label of each
ProfileMain setImbalanced variantLabelDescription
In use7201,1200Phone in ordinary use, not reset in the window; a few were reset long ago
Updated2404000Took a system update in the last month; setup flags and recovery runs move; no reset
New device1601800First set up in the last month; looks young; not reset
Reset, older2802000Reset 31 to 450 days before acquisition (many just past the 30-day line)
Reset, recent6001001Reset 0 to 30 days before acquisition

Traps built in

Each one is described in the teaching guide, with the numbers. Try to find them yourself first.

Traps in DS-0005 and where to find them
TrapWhereWhat it teaches
Leakage columnreviewer_reset_note, in train.csv and test.csvIt is 1 on 93 percent of resets and 3 percent of the rest, because a reviewer typed it after reading the log. Scores rise with it and nothing warns you. It would not exist at triage time.
Class imbalancevariants/imbalanced/About 5 percent positives (66 of 1,400 train rows, 34 of 600 test rows). Accuracy looks high while most resets are missed; thresholds and class weights trade precision for recall.
Clock-tampered rowsclock_tampered in the answer files; 157 of 2,000 rowsAges stamped after a clock change are shifted by 3 to 400 days, so a reset reads older or in the future. The same model scores clearly lower on these rows.
Look-alikesProfiles new_device, updated, some in_useRecent setup flags and young apps without any reset. "Recent setup" is not "reset".
Blanks that differRecovery columns; 536 rows have no recovery fileNo file, a file with no wipe entry, and a clock that prints a year near 1970 (88 rows) are three different situations.
Flags that fire on everyoneepoch_zero_file_time, future_file_time_48hOn about 89 and 36 percent of rows whatever the label. A feature that is nearly always on says little.

What the included baseline scores

600 test rows, 175 of them positive. These are printed by code/evaluate.py. The score of the default logistic regression is not printed here, because question 4 of the challenge asks for it: run the baseline and read it off.

Baseline scores on the DS-0005 test file
ModelAccuracyPrecisionRecall
Always predict "not reset"0.708not defined0.000
Decision stump (one rule)0.8570.9890.514
Logistic regression with the leakage column0.9770.9600.960
Imbalanced variant, logistic regression0.9650.9330.412
Imbalanced variant, class weights balanced0.9450.5080.941

Two limits. The accuracy of any model here is higher than it would be on real phones, because the labels are exact, the features come from a short generator and train and test are the same process; the teaching guide lists the reasons. And a score is an indicator of how a method behaves on this table. It is consistent with the method working, and it does not prove that it works on a device.

Three rows, read side by side

Three phones from train.csv. A rule that says "the setup flag is under 30 days old, so it was reset" calls all three resets and is right once.

Three rows of train.csv compared
ColumnD17064D60032D16967
reset_30d100
recovery_file_present110
recovery_wipe_entries10blank
last_recovery_wipe_age_days9.33blankblank
setup_complete_age_days9.3224.1819.07
account_added_age_days0.7824.190.70
earliest_app_install_age_days9.2524.231112.38
apps_installed_30d_fraction1.0001.0000.676
boot_count_since_setup21613
reviewer_reset_note100
Profile (reference file)Reset, recent (9.33 days)New deviceIn use

D17064 has a wipe entry 9.33 days old, a setup flag 9.32 days old, and its oldest app installed 9.25 days ago. Three sources point to one moment, which is consistent with a reset.

D60032 was set up 24 days ago and every app arrived after that, but its recovery history holds no wipe. It is a new phone, so the young app times are consistent with a first setup and say nothing about a reset.

D16967 has no recovery file, a recent setup flag, a recently added account and two thirds of its apps installed lately, but its oldest app is 1,112 days old. The recent activity is real; the reset is not. Only the oldest app and the profile column in the reference file tell you so.

D17064 is also the only one of the three with reviewer_reset_note = 1: the note a reviewer wrote after reading the recovery log. That is the leakage trap in miniature. All values are invented.

Try it with free tools

Python 3 and a text editor are enough. A spreadsheet or a SQLite browser helps for the first look.

  1. Unzip and look. Open README.txt and DATA_DICTIONARY.md in a text editor, then train.csv in a spreadsheet. Count the blank cells in each column.
  2. Run the baseline. In the folder, run python code/baseline_stdlib.py --train train.csv --test test.csv --out predictions.csv. It trains in a few seconds.
  3. Score it. Run python code/evaluate.py predictions.csv test_answers.csv. It prints accuracy, precision and recall. Write them down.
  4. Break it on purpose. Add --keep-leak and score again (accuracy 0.977, as in the table above). Then run the same two commands inside variants/imbalanced and read the recall.
  5. Slice it. Add --by clock_tampered,profile to the score command. Which slice fails first?
  6. Query it. Import train.csv into a SQLite browser as a table named train and try the queries below. Then open TEACHING_GUIDE.md for ten things to try and the pitfalls.
SQL on train.csv (import empty fields as NULL)
-- the leakage column: 0.030 for not reset, 0.929 for reset
SELECT reset_30d, COUNT(*) AS n, ROUND(AVG(reviewer_reset_note), 3) AS note_rate
FROM train GROUP BY reset_30d;

-- a clock check that fires on nearly everyone: 0.887 in both classes
SELECT reset_30d, ROUND(AVG(epoch_zero_file_time), 3) FROM train GROUP BY reset_30d;

-- one trustworthy rule: a recent, plausible wipe entry is right 266 times in 271
SELECT COUNT(*), SUM(reset_30d) FROM train
WHERE last_recovery_wipe_age_days <= 30 AND recovery_clock_implausible = 0;

The third query finds only the phones that have a recovery file, so it misses the rest of the positives. Try the challenge: Train and doubt a reset classifier (Practitioner).

Get the dataset

One zip, 182,901 bytes. It holds 17 files: the tables, the answers, the guide, the code and the generator. Check it against the hash below.

Download factory-reset-classifier-1.0.zip (179 KB)

Served from this site, versioned. The manifest and the hashes are linked below.

SHA-256 of the zip

d1a9b3421d6c32f1553ace253e7515906018b858685164f91e719f781427f308

The loose files, manifest.json and SHA256SUMS.txt, sit beside the zip on this site. To rebuild every byte yourself, run python generate.py from the generator folder (Python 3, standard library only); change SEED for a different draw.

Licence

Data: Creative Commons Attribution 4.0 (CC BY 4.0). You may copy, share and adapt the tables, for teaching, research, casework and commercial work. You must give credit with the citation below, link the licence and say if you changed the files. The full text is at creativecommons.org/licenses/by/4.0 and in LICENCE.txt inside the zip.

Code: MIT. generate.py, the baselines and the evaluation script. Keep the copyright notice with copies.

Please keep the hashes beside any copy you share, and say that the files are synthetic. Do not present them as taken from real phones or real cases. The dataset is provided as is, without warranty.

Hashes

Check every file after download. The set hash is the SHA-256 of manifest.json, which lists the size and hash of every other file and the counts quoted on this page.

Set hash (SHA-256, manifest.json)

22621655320ddf47b86af1d9963b100976ef4032f3c630586c5c9b9ac5140911
Files in DS-0005 with SHA-256 hashes
FileContentSizeSHA-256
factory-reset-classifier-1.0.zipArchive182,901 bytes
d1a9b3421d6c32f1553ace253e7515906018b858685164f91e719f781427f308
train.csvTrain rows with labels118,747 bytes
0a76b2e04b0c6cd54da59abbf258916a8d4dd83068865dd0aedeecd32b7dd622
test.csvTest rows, no labels49,447 bytes
442bcd1f2c157c70207b20e168acb6a430ea2cd00d78df30d2aafe222e2a0987
test_answers.csvTest labels and slice columns15,335 bytes
cdca422480cd23c26cbf5319bfba778e73dbf45983fab122444f7d72173ab312
train_reference.csvSlice columns for train rows32,876 bytes
b3e91dfc2c871f9df4a886e385ee88518f826ddb9d3324035a4daaf60860af7e
variants/imbalanced/train.csvImbalanced variant: train.csv118,267 bytes
9e3cca5476312ae082fb280900da024b45a71aac51e22b80b4fa988d551d9da4
variants/imbalanced/test.csvImbalanced variant: test.csv49,698 bytes
7cf83f8913ee3d1972f65775e69225a8ee3e01d31166b5dd947ae17da5b20fc2
variants/imbalanced/test_answers.csvImbalanced variant: test_answers.csv13,694 bytes
c84f9115b5fd376ed14f011090cc1012e3014432d7d7fc67bb6efcf7ea619bca
variants/imbalanced/train_reference.csvImbalanced variant: train_reference.csv28,914 bytes
7225f3ad3ff4ae2d12610bbd1fc4c369ead2000d120de91e4d3d67998a0e0075
DATA_DICTIONARY.mdData dictionary6,126 bytes
543985b4567d458bf00068aeed811840ca7591b8eb135dcf67b33d55d5220826
data_dictionary.csvData dictionary (table)4,327 bytes
e47b13b0f31e4e060c9d398c92a82cc126f9b53e6f72ca06bb4bc747612f8202
TEACHING_GUIDE.mdTeaching guide11,282 bytes
c627047f6d794284d167b99fc358c3d096620527069d60b46b0050d48092ac73
README.txtNotes2,216 bytes
ae42e313e267c6ef98f24b84763c5709b98a174a200e1c1c1a4b8be3ac593a2a
LICENCE.txtLicences1,518 bytes
7b0fda7772b65d4b9fe0a2253cefc3c90cf4e9cc3b6e6864b678db0e8bfb594b
code/baseline_stdlib.pyBaseline (standard library)8,623 bytes
abf5a50e4eda20adcee604dc4e2bddc92e02134fe881488845caeac780be53cc
code/baseline_sklearn.pyBaseline (scikit-learn, optional)4,426 bytes
9132e0ae957e12f0c22963281eb2e0c569dfe77ccafe4527bca02438da5c0b3e
code/evaluate.pyEvaluation script3,530 bytes
68695af9bb090e1965b0aef3d98699113fa2a7bcc8c7f0114f4cb40bce11d94e
generator/generate.pyGenerator (MIT)30,063 bytes
0765e08baeb01f82df12316ce334d5e6548e16a6e2e0934ea5d66d307d272954
manifest.jsonManifest (file, bytes, SHA-256, summary counts)5,875 bytes
22621655320ddf47b86af1d9963b100976ef4032f3c630586c5c9b9ac5140911
SHA256SUMS.txtThe same hashes in sha256sum format1,661 bytes
77e180708e5c29ef77eecba6f7648ee256e2cd7ed48295ab661bbc8c8d1a35f7

The zip holds the same files as the folder (the zip itself, manifest.json and SHA256SUMS.txt excepted). Together those files are 498,966 bytes before compression. Hashes and sizes on this page are read from manifest.json.

Cite this dataset

Cite as

Addai, D., SectraTEK Lab (2026). Factory-reset classifier set (DS-0005), version 1.0. SectraTEK Lab Datasets. https://lab.sectratek.com/datasets/factory-reset-classifier/. SHA-256 of manifest.json 22621655320ddf47b86af1d9963b100976ef4032f3c630586c5c9b9ac5140911

Citation guide
BibTeX
@dataset{sectratek-lab-ds-0005,
  author    = {Addai, Daniel},
  title     = {Factory-reset classifier set},
  year      = {2026},
  version   = {1.0},
  publisher = {SectraTEK Lab},
  url       = {https://lab.sectratek.com/datasets/factory-reset-classifier/},
  note      = {DS-0005, SHA-256 of manifest.json 22621655320ddf47b86af1d9963b100976ef4032f3c630586c5c9b9ac5140911}
}

Related

Datasets carry their own licence. This one is CC BY 4.0 for data and MIT for code. Articles are CC BY 4.0. All licences.