Skip to content

Commit fc9f1ed

Browse files
author
Bram Vanroy
authored
Merge pull request #25 from BramVanroy/24-wrong-column-names-in-output
improve CoNLL-U fields
2 parents 5e82adc + 0df7cc5 commit fc9f1ed

6 files changed

Lines changed: 89 additions & 27 deletions

File tree

HISTORY.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# History
22

3+
## 3.4.0 (April 2nd, 2023)
4+
5+
User @matgrioni rightfully pointed out that the default fields in the library are not the real CoNLL-U fields. They
6+
should be in all caps, and for xpostag it should be XPOS and for upostag UPOS. Additionally, the user asked for more
7+
control over these fields. This release accommodates that request.
8+
9+
- **[formatter]** Breaking change: `CONLL_FIELD_NAMES` now is in line with the CoNLL-U descriptor field names:
10+
"ID", "FORM", "LEMMA", "UPOS", "XPOS", "FEATS", "HEAD", "DEPREL", "DEPS", "MISC". These are the new default fields.
11+
- **[formatter]** New parameter: `field_names` that allows you to override the fields that were described above.
12+
Simply add a dictionary of `{"default field name": "new field name"}`, e.g. `{"UPOS": "upostag"}
13+
314
## 3.3.0 (January 17th, 2023)
415

516
Since spaCy 3.2.0, the data that is passed to a spaCy pipeline has become more strict. This means that passing

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ To illustrate, here is an advanced example, showing the more complex options:
131131
- `conversion_maps`: a two-level dictionary that looks like `{field_name: {tag_name: replacement}}`. In
132132
other words, you can specify in which field a certain value should be replaced by another. This is especially useful
133133
when you are not satisfied with the tagset of a model and wish to change some tags to an alternative0.
134+
- `field_names`: allows you to change the default CoNLL-U field names to your own custom names. Similar to the
135+
conversion map above, you should use any of the default field names as keys and add your own key as value.
136+
Possible keys are : "ID", "FORM", "LEMMA", "UPOS", "XPOS", "FEATS", "HEAD", "DEPREL", "DEPS", "MISC".
134137

135138
The example below
136139

@@ -168,13 +171,23 @@ The snippets above will output a pandas DataFrame by using `._.pandas` rather th
168171
`._.conll_pd`, and all occurrences of `nsubj` in the deprel field are replaced by `subj`.
169172

170173
```
171-
id form lemma upostag xpostag feats head deprel deps misc
174+
ID FORM LEMMA UPOS XPOS FEATS HEAD DEPREL DEPS MISC
172175
0 1 I I PRON PRP Case=Nom|Number=Sing|Person=1|PronType=Prs 2 subj _ _
173176
1 2 like like VERB VBP Tense=Pres|VerbForm=Fin 0 ROOT _ _
174177
2 3 cookies cookie NOUN NNS Number=Plur 2 dobj _ SpaceAfter=No
175178
3 4 . . PUNCT . PunctType=Peri 2 punct _ SpaceAfter=No
176179
```
177180

181+
Another initialization example that would replace the column names "UPOS" with "upostag" amd "XPOS" with "xpostag":
182+
183+
```python
184+
import spacy
185+
186+
187+
nlp = spacy.load("en_core_web_sm")
188+
config = {"field_names": {"UPOS": "upostag", "XPOS": "xpostag"}}
189+
nlp.add_pipe("conll_formatter", config=config, last=True)
190+
```
178191

179192
#### Reading CoNLL into a spaCy object
180193

spacy_conll/formatter.py

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,39 +6,46 @@
66
from spacy.tokens import Doc, Span, Token
77
from spacy_conll.utils import PD_AVAILABLE, merge_dicts_strict
88

9-
109
if PD_AVAILABLE:
1110
import pandas as pd
1211

1312
CONLL_FIELD_NAMES = [
14-
"id",
15-
"form",
16-
"lemma",
17-
"upostag",
18-
"xpostag",
19-
"feats",
20-
"head",
21-
"deprel",
22-
"deps",
23-
"misc",
13+
"ID",
14+
"FORM",
15+
"LEMMA",
16+
"UPOS",
17+
"XPOS",
18+
"FEATS",
19+
"HEAD",
20+
"DEPREL",
21+
"DEPS",
22+
"MISC",
2423
]
2524

2625

2726
@Language.factory(
2827
"conll_formatter",
29-
default_config={"conversion_maps": None, "ext_names": None, "include_headers": False, "disable_pandas": False},
28+
default_config={
29+
"conversion_maps": None,
30+
"ext_names": None,
31+
"field_names": None,
32+
"include_headers": False,
33+
"disable_pandas": False
34+
},
3035
)
3136
def create_conll_formatter(
32-
nlp: Language,
33-
name: str,
34-
conversion_maps: Optional[Dict[str, Dict[str, str]]] = None,
35-
ext_names: Optional[Dict[str, str]] = None,
36-
include_headers: bool = False,
37-
disable_pandas: bool = False,
37+
nlp: Language,
38+
name: str,
39+
conversion_maps: Optional[Dict[str, Dict[str, str]]] = None,
40+
ext_names: Optional[Dict[str, str]] = None,
41+
field_names: Dict[str, str] = None,
42+
include_headers: bool = False,
43+
disable_pandas: bool = False,
3844
):
3945
return ConllFormatter(
4046
conversion_maps=conversion_maps,
4147
ext_names=ext_names if ext_names else {},
48+
field_names=field_names if field_names else {},
4249
include_headers=include_headers,
4350
disable_pandas=disable_pandas,
4451
)
@@ -77,8 +84,11 @@ class ConllFormatter:
7784
on the first level, and the conversion map on the second.
7885
E.g. {'lemma': {'-PRON-': 'PRON'}} will map the lemma '-PRON-' to 'PRON'
7986
:param ext_names: dictionary containing names for the custom spaCy extensions. You can rename the following
80-
extensions: conll, conll_pd, conll_str.
81-
E.g. {'conll': 'conll_dict', 'conll_pd': 'conll_pandas'} will rename the properties accordingly
87+
extensions (use as keys): 'conll', 'conll_pd', 'conll_str'. E.g. {'conll': 'conll_dict', 'conll_pd': 'conll_pandas'}
88+
will rename the properties accordingly
89+
:param field_names: dictionary containing names for custom field names in case you do not want to use default
90+
CoNLL-U field names. You can rename the following fields (use as keys): 'ID', 'FORM', 'LEMMA', 'UPOS', 'XPOS',
91+
'FEATS', 'HEAD', 'DEPREL', 'DEPS', 'MISC'. E.g. {'UPOS': 'upostag'} will rename the field name UPOS accordingly.
8292
:param include_headers: whether to include the CoNLL headers in the conll_str string output. These consist
8393
of two lines containing the sentence id and the text as per the CoNLL format
8494
https://universaldependencies.org/format.html#sentence-boundaries-and-comments.
@@ -88,13 +98,16 @@ class ConllFormatter:
8898

8999
conversion_maps: Optional[Dict[str, Dict[str, str]]] = None
90100
ext_names: Dict[str, str] = field(default_factory=dict)
101+
field_names: Dict[str, str] = field(default_factory=dict)
91102
include_headers: bool = False
92103
disable_pandas: bool = False
93104

94105
def __post_init__(self):
95106
# Set custom attribute names so that users can access them with their own preference
96107
default_ext_names = {"conll_str": "conll_str", "conll": "conll", "conll_pd": "conll_pd"}
97108
self.ext_names = merge_dicts_strict(default_ext_names, self.ext_names)
109+
default_field_names = {fname: fname for fname in CONLL_FIELD_NAMES}
110+
self.field_names = merge_dicts_strict(default_field_names, self.field_names)
98111

99112
# Initialize extensions
100113
self._set_extensions()
@@ -197,7 +210,7 @@ def _set_token_conll(self, token: Token, token_idx: int = 1) -> Token:
197210
)
198211

199212
# turn field name values (keys) and token values (values) into dict
200-
token_conll_d = OrderedDict(zip(CONLL_FIELD_NAMES, token_conll))
213+
token_conll_d = OrderedDict(zip(list(self.field_names.values()), token_conll))
201214

202215
# convert properties if needed
203216
if self.conversion_maps:

tests/conftest.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,22 @@ def spacy_conllparser():
6363

6464
@pytest.fixture
6565
def spacy_ext_names():
66-
return init_parser("en_core_web_sm", "spacy",
67-
ext_names={"conll": "conllu", "conll_str": "conll_text", "conll_pd": "pandas"}
68-
)
66+
return init_parser(
67+
"en_core_web_sm", "spacy",
68+
ext_names={"conll": "conllu", "conll_str": "conll_text", "conll_pd": "pandas"}
69+
)
6970

7071

7172
@pytest.fixture
7273
def spacy_conversion_map():
7374
return init_parser("en_core_web_sm", "spacy", conversion_maps={"lemma": {"-PRON-": "PRON"}})
7475

7576

77+
@pytest.fixture
78+
def spacy_field_names():
79+
return init_parser("en_core_web_sm", "spacy", field_names={"UPOS": "upostag"})
80+
81+
7682
@pytest.fixture
7783
def spacy_disabled_pandas():
7884
return init_parser("en_core_web_sm", "spacy", disable_pandas=True)
@@ -112,6 +118,11 @@ def spacy_ext_names_doc(spacy_ext_names):
112118
return spacy_ext_names(single_sent())
113119

114120

121+
@pytest.fixture
122+
def spacy_fields_names_doc(spacy_field_names):
123+
return spacy_field_names(single_sent())
124+
125+
115126
@pytest.fixture
116127
def spacy_conversion_map_doc(spacy_conversion_map):
117128
return spacy_conversion_map(single_sent())

tests/test_conversion_maps.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
def test_conversion_map_pronoun(spacy_conversion_map_doc):
22
pronoun = spacy_conversion_map_doc[0]
33
# Verify that -PRON- was changed to PRON
4-
assert pronoun._.conll["lemma"] == "he"
4+
assert pronoun._.conll["LEMMA"] == "he"
55
# lemma is the third column
66
assert pronoun._.conll_str.split("\t")[2] == "he"
7-
assert pronoun._.conll_pd["lemma"] == "he"
7+
assert pronoun._.conll_pd["LEMMA"] == "he"

tests/test_field_names.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
def test_fields_names_doc_pd(spacy_fields_names_doc):
2+
assert spacy_fields_names_doc._.conll_pd.columns[3] == "upostag"
3+
4+
5+
def test_fields_names_sents_conll(spacy_fields_names_doc):
6+
for sent in spacy_fields_names_doc.sents:
7+
assert sent._.conll_pd.columns[3] == "upostag"
8+
9+
10+
def test_fields_names_token_conll(spacy_fields_names_doc):
11+
for token in spacy_fields_names_doc:
12+
assert "UPOS" not in token._.conll
13+
assert "upostag" in token._.conll
14+
assert token._.conll_pd.index[3] == "upostag"

0 commit comments

Comments
 (0)